104
59

More than 5 years have passed since last update.

TypeScriptでswitch式を実装する。

Last updated at Posted at 2019-03-05

背景

switch文が好きじゃなかったのでTypeScriptでswitch式を書いてみる。

switch文のイケてないところ

  • 値を返せない
  • 一致条件しか使えない
  • なんかかっこわるい

値を返せないのは関数型スタイルで書きたいときにつらい。
あとなんかとりあえずかっこわるい。

劇的ビフォーアフター

Before

普通にswitch文でこう書くところを、

const hoge = 1

switch (hoge) {
  case 1:
    console.log("A");
    break;
  case 2:
    console.log("B");
    break;
  case 3:
    console.log("C");
    break;
  default:
    console.log("default");
    break;
}

After

こう書けるようにする。

const hoge = 1

const result = when(hoge)
  .on(v => v === 1, () => "A")
  .on(v => v === 2, () => "B")
  .on(v => v === 3, () => "C")
  .otherwise(() => "default")

console.log(result)

もちろん型の恩恵も得られる。型注釈を省略せずに書くとこうなっている。

const result: string = when(hoge)
  .on((v: number) => v === 1, (): string => "A")
  .on((v: number) => v === 2, (): string => "B")
  .on((v: number) => v === 3, (): string => "C")
  .otherwise((): string => "default")

ケースごとに異なる型を返してみると、型の恩恵を得られている感がある。

const result: string | number | string[] = when(hoge)
  .on((v: number) => v === 1, (): string   => "A")
  .on((v: number) => v === 2, (): boolean  => 100)
  .on((v: number) => v === 3, (): string[] => ["aaa", "bbb"])
  .otherwise((): string => "default");

マッチする条件は関数で書くので、switch文と違って好きな条件で書ける。

const result = when("abcde")
  .on(v => v.length < 3, () => "length less than 3.")
  .on(v => v.startsWith("a"), () => "start with 'a'.")
  .otherwise(() => "default")

console.log(result) // -> start with 'a'.

otherwise()を呼ぶ前に値を使うとコンパイルエラーになるので、switch文でいうdefaultを強制されている感じ。

const result = when(hoge)
  .on(v => v === 1, () => "A")
  .on(v => v === 2, () => "B")
  .on(v => v === 3, () => "C")
  // .otherwise(() => "default")

result.startsWith('a') // string型ではないのでコンパイルエラー!!!
result.otherwise(() => 'default').startsWith('a') // OK

v => v === 1みたいにアロー関数を毎回書くのが面倒なら、比較用に関数を作るとシンプルになる。

const eq = <T>(val1: T) => (val2: T) => val1 === val2

const result = when(1)
  .on(eq(1), () => "A")
  .on(eq(2), () => "B")
  .on(eq(3), () => "C")
  .otherwise(() => "default")

console.log(result) // -> "A"

ソースコード

type ChainedWhen<T, R> = {
  on: <A>(pred: (v: T) => boolean, fn: () => A) => ChainedWhen<T, R | A>;
  otherwise: <A>(fn: () => A) => R | A;
};

const match = <T, R>(val: any): ChainedWhen<T, R> => ({
  on: <A>(pred: (v: T) => boolean, fn: () => A) => match<T, R | A>(val),
  otherwise: <A>(fn: () => A): A | R => val
});

const chain = <T, R>(val: T): ChainedWhen<T, R> => ({
  on: <A>(pred: (v: T) => boolean, fn: () => A) =>
    pred(val) ? match(fn()) : chain<T, A | R>(val),
  otherwise: <A>(fn: () => A) => fn()
});

const when = <T>(val: T) => ({
  on: <A>(pred: (v: T) => boolean, fn: () => A) =>
    pred(val) ? match<T, A>(fn()) : chain<T, A>(val)
});

ざっくり説明:

  • when: マッチしたらmatch()、しなかったらchain()を返す
  • chain: マッチしたらmatch()、しなかったらchain()を返す。otherwiseつきで返すのがwhen()との違い。
  • match: 「マッチしてもしなくても関係なく渡された値を返す」関数を返す。

参考

104
59
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
59