kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Klassic言語の現状のステータス

Klassic は自分が新たに去年くらいから開発し始めたプログラミング言語です。

まだ確固たる設計思想があるわけではなく、色々な構文や機能について試しに導入してみて、テストを書いて…みたいなのを繰り返してる 段階です。現状のKlassic言語の大雑把な特徴としては

  • ML多相に類似した型システム(ちゃんとした、というか、普通の型推論があります)
  • 静的スコープ(現在では当たり前過ぎますが)
  • 第一級関数(今では当たり前過ぎますが、組み込みの関数型があって、というあたりは、より、いわゆる「関数型言語」風味に寄せています
  • ヒアドキュメント
    • Rubyとかのあれですね。これは、ヒアドキュメントの構文をきちんと理解するための実験過程で入りました
  • スペースセンシティブなあれこれ
    • 色々なものをスペースセンシティブな、つまり、空白に意味を持たせてみると便利になるのではないか、という実験
    • スペースセンシティブかつ改行センシティブなリストリテラル
    • スペースセンシティブかつ改行センシティブなマップリテラル
    • スペースセンシティブかつ改行センシティブな集合リテラル
    • 改行センシティブな構文
  • cleanup式
  • Java FFI
    • これは、単純に色々な機能を実現しようとしたときに、あると便利なので暫定的に入っています

ML多相に類似した型システム

よーするに「普通の」型推論が行われるということです。「普通の」というのは単一化ベースの型推論で、(おそらく)型注釈 は書かずに全部推論できる(はず)という話です。

たとえば、

def fact(n) = if(n < 2) 1 else n * fact(n - 1)

は、ちゃんとfact : Int => Intと推論してくれます。

mutable変数があるのに、多相性に制限つけてないので、mutable変数を考えると 健全でなくなるという大昔に解決された問題までそのまま残っています(笑)。これは、当然ながら型システムの性質として好ましく ないので、既存の解決法を流用するか、独自になんかそれっぽい解決法を適用してみる予定です

ヒアドキュメント

説明はあまりいらないと思いますが、

println(<<TAG1 + <<TAG2);
tag1
TAG1
tag2
TAG2

という感じのがちゃんとパーズされて動きます(一行に複数のヒアドキュメント開始が持てる(これはRubyもそうなので真似しています)点がポイントです)

第一級関数

map([])((x) => x + 1)

無名関数の文法はScalaの影響をかなり受けています。型クラスとかまだないので、x -> x + 1xの使われ方から、Int => Intとして推論されます。普通です。

スペースセンシティブかつ改行センシティブなリストリテラル

この辺の文法的な実験、というのが実は自分が一番やりたかったことです。

[1 2]

[1, 2]

と解釈されますし、

[[1 2]
 [3 4]
 [5 6]]

は、

[[1, 2],
 [3, 4],
 [5, 6]]

と解釈されます。要素を改行で区切るのは、trailing commaの利点も受けられるし、構文ノイズも入らないし、「思った通りにパーズされる」ので、割と良さげです。

スペースセンシティブかつ改行センシティブなマップリテラル

リストリテラルと同じような文法をマップリテラルにも入れてみました、というお話。

val points = [
  %["x":1 "y":1 "z":2
    "x":1 "y":1 "z":3]
]

%[で始まるのがマップリテラルで、キー・値ペアが空白でも改行でも区切ることができます。これもまあ、割と思った感じにパーズされます。集合リテラルの文法はリストリテラルとほとんど同じなので省略。

改行センシティブな構文

最近の言語には割とありがちですが、改行が色々なものの区切りになることができます。ただし、純粋に改行が来ると区切る、のだと困るケースもあるので、その辺はパーザが頑張ってます(この辺をどう頑張るかは言語によって異なると思いますが、Klassicでは割と単純な方法を採用しています)。

基本的なケースとして、

val x = 1
println(x)

このようなのはOKです。

val x = 1
+ 2
println(x)

このケースでは、1の後ろの改行で、行が終わるので宣言の終わりだと解釈するため、構文解析に失敗しますが

val x = 1 +
2
println(x)

のケースでは、1 +を読んだ段階で、これは行継続を意図してるっぽいと判断して、改行は意味がないものとして扱います。

cleanup式

色々な式について、その式が評価後に必ず実行されるコード(ただし、式の結果値は捨てられる)をくっつけることができます。

mutable i = 0
while(i < 10) {
  i = i + 1
} cleanup {
  println(i) // 10 is printed
}

後始末構文をもっと軽量にしてみたらこんな感じかなという割とどうでもいい実験です。

Java FFI

普通に、ScalaとかKotlinとかっぽい感じでJavaのメソッドが呼べます。

val newList = new java.util.ArrayList
foreach(a in [1, 2, 3, 4, 5]) {
  newList.add((a :> Int) * 2)
}
newList

ここで、:>は型キャスト式です。昔の時点で必要だったのでそのままコピペしてみたのですが、今のバージョンでは既に要らない気がします。

こんな感じで、今のところ、構文的な部分以外は何も特別ではないのですが、今後は言語機能上の実験も色々やっていきたいなと思うところです。今後の方向性として、structural type systemをまず入れる予定で、それに、open classを加えて、メソッド拡張を含めた場合にも型が推論されるような感じにするのがまずあります。

なお、構文はかなりScalaの影響を受けています(やっぱり今の自分にとって書きやすい構文の方に寄せたくなる)。