TypeScript/ES6のダサいswitch文を克服する

  • 10
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この記事はTypeScript Advent Calendar 2014 - Qiitaのやつです。他にネタがないんで未来に向けて投げます。

switch break…

coffeeやrubyやscalaをやってると、なんでjavascript/typescriptはswitchとifが式じゃなくて文なんだ!!!ちくしょう!!!という気分になりますよね。

というわけでtypescript/es6用にscalaのmatch式っぽいのを自作しました。ご査収ください。

https://github.com/mizchi/mz-match

マッチャーの仕様

  • === の比較
  • bool値を返す関数
  • クラス

こう書けます。

class A {}

var ret = match(new A(),
    'foo'         , ()=> 'this is foo',
    A             , ()=> 'this is instance of A',
    (v) => v === 1, ()=> 'this is 1', 
    () => 'not hit'
);

仕様としては (target, matcher1, ret1, matcher2, ret2, ... default); という感じです。最後に渡したのはdefaultになります。

偶数/奇数で判定しているのが気持ち悪いですが他の記法を思いつきませんでした。オブジェクトリテラルのキー値はかならず
stringなので…。ES6 Map使おうとしたけどやっぱりなんか違いました。

クッソ賢いモナディックなマッチングも考えたんですがあんま使うケース思いつかなくてだるくなってやめました。雑に賢くするんだったらsinonのマッチャーをそんままパクってくるといいと思います。

で、typescript関係あるの?

最初に型定義をこんな風に書きました

declare function match(...args: any[]): any;
declare function match<T>(...args: any[]): T;

なんかもう気持ちだけって感じなので、あんまり嬉しくない。
なので制約を課して型にやさしくしたい!!!

  • 関数マッチャーしか使わない(ポテンシャルとしては関数マッチャーは万能)
  • ジェネリクスでmatch<T, U>として、target:T, matcher1, path1, ... _default: ()=> U をn番目まで用意すればよい

n番目まで人間が書くのはありえんので、雑にcoffeeで書いた。

console.log '''
declare function match(...args: any[]): any;
'''

nthPair = (nth)-> "matcher#{nth}: (t: T) => boolean, path#{nth}: ()=> U"
for n in [1..12]
  pairs = (nthPair(i) for i in [1..n])
  typeString = pairs.join ', '
  console.log "declare function match<T, U>(target: T, #{typeString} , _default?: ()=> U): U;"

coffee typegen.coffee > match.d.tsで生成!やったーーーー

declare function match(...args: any[]): any;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T, matcher8: (t: T) => boolean, path8: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T, matcher8: (t: T) => boolean, path8: ()=> T, matcher9: (t: T) => boolean, path9: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T, matcher8: (t: T) => boolean, path8: ()=> T, matcher9: (t: T) => boolean, path9: ()=> T, matcher10: (t: T) => boolean, path10: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T, matcher8: (t: T) => boolean, path8: ()=> T, matcher9: (t: T) => boolean, path9: ()=> T, matcher10: (t: T) => boolean, path10: ()=> T, matcher11: (t: T) => boolean, path11: ()=> T , _default?: ()=> T): T;
declare function match<T, U>(target: T, matcher1: (t: T) => boolean, path1: ()=> T, matcher2: (t: T) => boolean, path2: ()=> T, matcher3: (t: T) => boolean, path3: ()=> T, matcher4: (t: T) => boolean, path4: ()=> T, matcher5: (t: T) => boolean, path5: ()=> T, matcher6: (t: T) => boolean, path6: ()=> T, matcher7: (t: T) => boolean, path7: ()=> T, matcher8: (t: T) => boolean, path8: ()=> T, matcher9: (t: T) => boolean, path9: ()=> T, matcher10: (t: T) => boolean, path10: ()=> T, matcher11: (t: T) => boolean, path11: ()=> T, matcher12: (t: T) => boolean, path12: ()=> T , _default?: ()=> T): T;

で、たとえばenum使ってこう書けるようになります

///<reference path='match.d.ts' />

declare var require: any;
declare var global: any;
global.match = require('./');

enum Params {
  A, B, C
}
var target = Params.A;
var ret = match<Params, string>(target,
    (v) => v === Params.A, ()=> 'this is A', 
    (v) => v === Params.B, ()=> 'this is B',
    (v) => v === Params.C, ()=> 'this is C'
);

うれしさ

結論

breakはCを引きずった辛い仕様だと思うしtypescript/es6が乗り越えられなかった壁なので、どうにかしたければこういうことをするハメになります。なぜか最近の言語であるはずのDartすらこうなってるのは涙がこみあげてきます。

まあ、たぶん諦める方が精神衛生的に良いです。