読者です 読者をやめる 読者になる 読者になる

TypedCoffeeScript on flowtypeの可能性

僕はcoffeescriptは人類がプログラミングにおいて堕落するために手に入れた最高のゆるふわ文法だと思っていて、これを殺すわけにはいかない、という気持ちがある。で、es6/typed annotationが跋扈するこの時代にふさわしいものに改造されないといけないとも思っている。最近だと、正直coffeescript方面のイノベーションはあんまりない。

そういうわけで、ちょっと前までtyped-coffee-scriptを作ってたんだけど、現状ある種の問題を迎えていて、開発が半年ほど止まっている。それをどうするかということを考えた結果、別の型付きaltjsに乗りゃいいじゃん、という発想に至った。

altjs on altjsってどうなの、っていう問題もあるが、基本的にflowtypeもtypescriptもes6~以降との標準化追従する方向性であり、そう大きな問題になることはない、と認識している。

尚、coffeeが生き残るべきかどうかは好き嫌いの話であるというのは重々承知しており、CoffeeScriptの文法そもそも嫌いなんですけど?という人がいたら、まあそういうのもあるよねという反応をします。

TypedCoffeeScriptはどういう言語か

mizchi/TypedCoffeeScript

現状、これが動く

# struct
struct Value<T, U>
    value :: U
struct Id<A, B>
    id :: Value<A, B>
obj :: Id<Int, String> =
  id:
    value: 'value'

# function type arguments
map<T, U> :: T[] * (T -> U) -> U[]
map = (list, fn) ->
  for i in list
    fn(i)
list :: String[] = map<Int, String> [1..10], (n) -> 'i'

# class type arguments
class Class<A>
  f :: Int -> Int
  constructor :: A -> ()
  constructor: (a) ->
c = new Class<Int>(1)

まあ結構頑張って実装したわけです。

既存のTyped CoffeeScriptの問題点

  • 自分で型システムごと実装するハメになり、工数爆発

ここに尽きる。僕はコンパイラとか大学等で習ったことがなかったので、パーサとかASTとか木構造書き換えとか全部その場で既存実装を調査したり勉強しながら作ってて、実際になんども手戻りが発生しており、今止まってる箇所が再帰的に型シンボルの調査を行う部分とジェネリクスで、推論器含めてかなりの部分を作り直さないといけないのがわかってて、その工数を捻出するのはおそらく無理、という判断をしている。

できなくもないが、趣味開発において、長時間の開発で減衰するモチベーションという一番大事な資源を見積もった結果、まったく足りない。給与が発生するんだったらいいけど!って金を集める手段を考えたけど、まあ厳しい。

なので発想をそもそも変えることにした。

CoffeeScriptのメリットはそもそも文法面でありそれ以外はいらないという発想

CoffeeScriptは主に文法にフィーチャした言語である。型システムを自前で実装する必要はない。なので型システムを提供してくれる言語を生成すればいいんだ!と考える。

ES Harmonyには type hintingというproposalがある。これは 型アノテーションを掛けるようにする、という文法上の提案であり、なのでそれを含むASTに変換すればよい。

現状、それを含む実装は、facebook/flowとそれが内部で使っているfacebook/esprimaであるということがわかった。

ES harmony type hintingの方向性

おそらくだけど、僕の見立てでは「ES Harmony(たぶん7)ではtype hintingの文法は定義するけど、この時点では型システムは定義しない。各自好き勝手に静的解析を流してください」って方向になると思う。AtScriptとTypeScriptとfacebook/flowを統一する統一型システムを作る時間は足りない。

なので既存実装に相乗りしたい。

最初の検討: TypeScriptがハックできるか

厳しい。AST含め、全てが自前で実装されている。しかも仕様化されてないので怖すぎる。本家の開発を理解できる程度のコード理解が必要で、その時間はない。

facebook/flowベースの可能性

requireベースで参照に型付けし、しかも裏でインクリメンタルに処理されている。これは僕の理想に近い。 とはいえ現状微妙に使い心地が悪い部分が残っており、適当に本家にイシュー出すなどしている。

flowはesprimaのフォークを使っており、既存のParser APIとある程度の互換がある。

今のtypedcoffeeのコード資産

CoffeeScriptReduxをForkしたtyped-coffee-scriptは、TypedCoffeeScript AST を持っており、コンパイルとはCoffeeScriptAST -> JavaScript ASTの変換をescodegenによって行う。Typed~としてはアノテーションの付与とその後のASTの型走査のみ実装しており、コンパイラに一切介入しないように設計してあるので、一行コメントアウトするだけで型走査は捨てられる。

TypedCoffeeScript v2では、型走査は捨てて、文法定義だけ再利用する。

とはいえ現状のコンパイラの出力ターゲットがes5なので、クラス情報が失われる。型があるのにクラス情報がないのはあんまり嬉しくない。なのでes6 classの仕様を含んだものに書き換えればよい。

facebook/esprimaのASTをターゲットにする

で、flowが使っているesprimaのfacebookのforkはこの仕様を含んでいるので、これに合わせて出力するASTを改造する必要がある。

次にASTから実際のコードを吐く必要があるのだが、facebook/jstransformerというリポジトリがそれに該当するようだ。今コードを読んでいるが…行単位で変形するという豪快な感じになっている。

というかよく考えたらflowもどうせASTで処理してるんだから、flowに直接ASTおくりつければいいじゃん!ってことに気づいた。が、今調べた感じそのようなオプションはなく、さすがにocamlを読むのは辛いので、将来的に出るという噂の、flowtypeがjs_of_ocamlコンパイルされるやつにオプションください!とイシュー出すなどして解決したい。

ちなみに、本家jashkenas/coffeescriptはASTという概念を持っていない。tokenをstreamで処理してコード生成するという気合に満ち溢れた設計である。

結論

  • coffeescirptは人類が堕落するための最高の文法を提案した
  • 現在の型走査実装は捨てる
  • pegで書かれた文法定義は再利用する
  • facebook/esprimaの仕様に合わせes6向けcoffeescriptターゲットにする
  • flowのASTを出力し、flowの型チェッカーに渡せるようにする。