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 (略) ハッカソンでやることがなくなりそうな勢い・・・

*1:ただ、現行の型推論で x のみ型を省略できるというのは少々分かりにくさを増やしてしまうことになるかも

*2:number とか独自クラスとか