JSX の進化速度が半端ない
気に入らない所を直して pull request 投げたら、取り入れられたので、8 日前に書いたエントリが過去のものとなっちゃいました。
関数型
以前の JSX では、関数型は
function(: int): string
のように書く必要がありました。
これはこれでそのまま使えるのですが、新たに
(int) -> string
という形式にも対応しました。
ちなみに、複数引数はカンマ区切りで
(int, boolean) -> string
のようになります。
カリー化された関数は、
function(: int): function(: number): string
の代わりに
(int) -> (number) -> string
と書けます。
引数を囲むカッコは、(今のところ) 省略不可能です。
これには 2 つの理由があります。
- この機能を追加したとき、JSX のパーサの能力が LL(1) だと思っていた
- 引数を追加したときにわざわざカッコまで補うのが面倒
一つ目の理由はもうすでに消えており(_preserveState/_restoreState がある)、残るは二つ目の理由だけですが、これは開発者に委ねようと思います。
この部分に関しては現状で割と満足しています。
無名関数
先のエントリには書いていないのですが、従来の JSX では無名関数を書くために function キーワードが必要となっており、ちょっと面倒でした。
function f(x: int): (int) -> int { return function(y: int): int { return x + y; }; }
最後のセミコロンを忘れがちで JSX のコードを書いていると発狂しそうでしたが、こう書けるようになりました。
function f(x: int): (int) -> int { return (y: int): int -> x + y; }
function キーワードがなくなり、また無名関数の中身が return 文のみの場合、省略できます。
ちなみに、もちろん
function f(x: int): (int) -> int { return (y: int): int -> { return x + y; }; }
と書くこともできます。
ここまでが、取り入れられた部分です。
この記法は関数型の記法に合わせてみたのですが、関数型を返す関数とはこのままでは相性が悪いです。
function hoge(f: (int) -> (int) -> int): int { return f(10)(20); } log this.hoge((x: int): (int) -> int -> (y: int): int -> x + y);
ですが、これも型推論が搭載されたことにより、ある程度緩和されています。
型推論
型推論が少し強化されました。
例えば先ほどのプログラムは、
function hoge(f: (int) -> (int) -> int): int { return f(10)(20); } log this.hoge((x) -> (y: int): int -> x + y);
と、一番外側の型を省略できるようになりました。
このエントリを書いてから 1 時間と経たないうちに、外側だけでなく内側の型も推論されるようになりました。
そのため、
function hoge(f: (int) -> (int) -> (int) -> int): int { return f(10)(20)(30); } log this.hoge((x) -> (y) -> (z) -> x + y);
と記述できます。
以下古い情報です。
ただし、更に増えると
function hoge(f: (int) -> (int) -> (int) -> int): int { return f(10)(20)(30); } log this.hoge((x) -> (y: int): (int) -> int -> (z: int): int -> x + y);
となり、根本的な解決には至っていません。
これを解決するためには、カリー関数用に記法を拡張し、
// 未実装の機能 log this.hoge((x: int)(y: int)(z: int): int -> x + y + z);
のように書けるようにすると、許容できる範囲内に収まるような気がします*1。
まぁ、JSX はカリー関数を多用するような層をターゲットにしていないと思われるため、割とアリな選択のように思えます。
この型推論は、引数の位置でしか推論されないため、
function hoge(x: int): (int) -> int { return (y) -> x + y; }
のような推論はできません。
これは、型を明示して
function hoge(x: int): (int) -> int { return (y: int): int -> x + y; }
とする必要があります。
戻り値の位置でも推論されるようになり、
function hoge(x: int): (int) -> int { return (y) -> x + y; }
と書けるようになりました。
ループ地獄
型推論が強化されたことから、map など使いやすくなったかな?と思ったのですが、これはまだのようです。
template がユーザからも使えるようになったので、使ってみようと思いましたが、これもうまくいきませんでした。
class _Main { static function main(args: string[]): void { var hoge = new Hoge.<string, string>(); var xs = hoge.map(["hoge", "piyo"], (s) -> s + "."); log xs; } } class Hoge<T, U> { function map(xs: T[], f: (T) -> U): U[] { var len = xs.length; var ret = new U[len]; for (var i = 0; i < len; i++) ret[i] = f(xs[i]); return ret; } }
これはうまくいくのですが、string 以外*2でインスタンス化しようとしても失敗してしまいました。
多分バグなので、そのうち修正されると思います。
修正されたようです。以下のコードが動きます。
class _Main { static function main(args: string[]): void { var hoge = new Hoge.<number, string>(); var xs = hoge.map([10, 20], (n) -> n.toString() + "!"); log xs; // => [ '10!', '20!' ] } } class Hoge<T, U> { function map(xs: T[], f: (T) -> U): U[] { var len = xs.length; var ret = new U[len]; for (var i = 0; i < len; i++) ret[i] = f(xs[i]); return ret; } }
ただ、クラスじゃなくて関数レベルで型パラメータが取れないと使い勝手はよくないですね。
また、template として実装されているため、挙動が読みにくいのもちょっと難点かもしれません。
インスタンス化してみるまでエラーが分からないという点が、思いのほか使い勝手が悪かったです。
その他
色々触っているうちに、色々と見えてきましたがそれはまたの機会ということで。
なんかこのまま進むと JS (略) ハッカソンでやることがなくなりそうな勢い・・・