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

TypedCoffeeScript進捗どうですか

型を入れ子にしてマッチできるようにした。

struct A {
  num :: Number
}

struct Point {
  x :: Number
  y :: {
    a :: A
    b :: String
  }
}
p :: Point = {x: 3, y: {a : {num: 4} , b : 'foo'}}

上の例では、左辺の型を右辺の推論結果が満たしていれば、代入は可能ということにしてある。余分なプロパティがあっても通る。ただし以降のpのシンボルは明示的に宣言したプロパティしか通らない。

struct Point {
  x :: Number
  y :: Number
}
p :: Point = {x: 3, y: 3, z: 4}

関心の分離を行うためにダウンキャストルールによってのみアクセス可能な変数が決まることにする。これは、動的言語の外部ライブラリから、使うたびに必要なスコープに切り出して使えるようにするため。動的言語に静的型チェックを持ち込むために現実的なアプローチだと思う。

それと全体的にリファクタしてだいぶましになった。今まで気合で書いていた部分を、ASTのノードオブジェクトの型チェックをそのまま使うようにした。

ついでにASTをなめ回りながら型アノテーションを追記しまくるような実装にした。暗黙的な型指定と明示的な型指定は処理の厳密度が違う。(元のcoffeeをそのままcompileできるようにするため)

例外はあるが全てのExpressionを引数に探索が走るようにできたので、探索漏れがない(はず)。 だいたいの基本であるオブジェクトリテラルの再帰チェックが動くようになったので、あとは気合で自分に必要な完成度までひた走るだけ(であってほしい)。

これからの予定

Int, Float

実はcoffee-scriptの処理系側で既に別シンボル扱いだったので、簡単っぽい。 継承ルールをまだ書いてないので、class A extends Bしたときなどの継承ルールちゃんと決めるとInt, Floatなどの組み込み型オブジェクトが自然に決まる。

前置

f :: Int -> Int
f = (x) -> x

こう書けるようにする。そんなに難しくはないが、元の処理系のスコープ処理に手を出す必要があるかもしれず、ちょっと怖い。

BinaryOperator

演算子オーバーロードがない処理系なので、式の方の推論が動けばそう難しくはない。ただルールの記述がめんどい。

Array

簡単。各要素においてインターフェースを満たすか検証すれば良い。型リテラルを決める必要はある。

関数の引数型チェック

引数の部分はArrayと似ているので、そんなに難しくない。 返り値のチェックは正直厳しい。一行関数でない場合は、明示的なreturn文だけとかにする方が良さそう。

(x :: Number) -> x # これは簡単。
(x :: Number) :: Number ->
  ... なんかいろいろやる
  return num #-> numがNumberならば通る

無理に色々やるより、それ以外のパターンは「関数宣言は返り値の宣言を信用する」といった方針がよさそうだが、推論が崩れた時にどこまで例外出すかは、まだ考える必要がある。基本的に推論で勝手につけたものと、ユーザーが指定した型が混ざってコンフリクトしたものは例外を吐く、みたいな方針ではいる。

メンバーアクセス時の型決定

createObj(Obj).x = 3 みたいなパターンはまだ考慮してない。Expressionの型追跡ができているので難しくない。

複数ファイルの型宣言

どういう方針にするかが悩ましい。割りきって自分でモジュール機構を追加したほうがよいかもしれない。コンパイル時のモジュールインポートは、Typed~とは関係なしにもともと欲しかった機能ではある。

あるいはファイルが渡された順に連結して上からパース、みたいなパターンは可能ではあるが、非常に暗黙的なのでエンバグした際のトレースが大変。トップレベルの型宣言リテラルだけ事前に収集して型チェック時にLazyに評価、みたいなことはできなくもないが、めんどい。

結論

めんどくないことからやる。まずArrayと引数チェックから作る。