effectで「普通のプログラミング言語」を作る:Yulangの設計思想
Yulangという新しい言語を作りました。Playgroundが既に動く状態で開発を継続中です。
要約
Yulangは「AE/Hを搭載した言語」というより、「AE/Hで普通のスクリプト言語(LL, Lightweight Language)を作る」試みです。
ユーザーが普段見るのは return、for、guard:、.once、名前付き引数、メソッド呼び出しのような普通の構文です。
一方で、それらの裏側では effect、handler、role、型推論が動いています。
この記事では、Yulangがなぜ perform を前面に出さないのか、そしてなぜ表層構文を軽く保とうとしているのかを説明します。
はじめに
YulangではLLの構文制御がAE/H (Algebraic Effects and Handlers)に基づいて動いています。そのため、Yulangでは次のようなコードも自然に動きます(答えは5です)。
my f x = return x
sub:
for x in 0..: if x == 5: f x
0
subがサブルーチンの略、returnがそこへ値を渡す式ですが、これは言語本体に焼き込まれた特別な脱出機構ではなく、Yulangでは Algebraic Effect と Handler によって定義されています。だからreturnを別の関数で言い換える、ということができてしまいます。
今回話題にしたいのは、「Yulangの何が既存のAlgebraic Effectをサポートする言語やLL言語と違うのか」という点です。その思想は「エフェクトは舞台装置であって主役ではない」「構文をできるだけ軽く保つ」ことです。
エフェクトは舞台装置であって主役ではない
舞台装置としてのエフェクト
今subとreturnの話をしましたが、AE/Hを表に出す言語では、ここに perform のような機構が現れることが多いです。少し専門的な話になりますが、代数的effectのoperationを起動するための、意味論上または表層上の目印です。しかしYulangでは、この目印をユーザーに直接書かせません。Yulangのsub-return構造は次で定義されます:
pub act sub 'a:
pub return: 'a -> never
pub sub(x: [_] _) = catch x: // `[_] _`は「好きなエフェクト`[_]`と好きな型`_`を取る」という意味
return a, _ -> a
a -> a
returnという関数があって、それの内容をcatchで受け取れる。本当にそれだけで、subに至っては具体的な型すらついていません。これがYulangの求める「舞台装置としてのエフェクト」なのです。
そして今まで見てきたように、Yulangではperform return xのような書き方をさせるのではなく、return xと書かせます。
これは単なる省略ではなく、effectを「ユーザーが意識する主役」ではなく「普通の構文を支える舞台装置」として扱うためです。
しかし、型はちゃんと推論されますので、
sub: α [sub<α>; β] → [β] α
という型がつき、でたらめなreturnは許されません。
エフェクト以外も「普通のLL」として便利にする
省略可能レコード
Simple-Subベースのアルゴリズムを使っているので、省略可能レコードが動きます。具体的には
my area {width = 1, height = 2} = width * height
area { width: 3 }
area {}
area { width: 3, height: 4 }
がそれぞれ6,2,12を出力してくれます。省略可能引数や名前付き引数はLLにも(OCamlにも)存在しますが、それらは「素直にレコードで渡し、ダメなら補完する」というアプローチで再現可能なのです。ちなみに型は
area: Mul<int | α> => {width?: α, height?: α} -> α | int
です。きちんと{width?: α, height?: α}が推論されていますね。型クラスが残っていますが、floatを入れたときのことを考えればまだ必要な制約だと分かります。
次に話したいのは、「構文をできるだけ軽く保つ」ということ。これは関数適用が一番分かりやすいと思います。
不必要な括弧や区切りを減らすというのを目標にしています。見ていきましょう。
C式適用、ML式適用、コロン適用、メソッド適用
Yulangには4種類の適用があります。
my ys = xs.map: \x -> f:
1 + 2 + g 3 4 + h i(5) + x
.filter: \x -> x > 0
ML適用g 3 4に関してはRubyに慣れた方なら「括弧どころかカンマも省略した関数適用がある」と考えていただいてよいです。C式関数適用i(5)には皆さんいつもお世話になっているでしょう。メソッド記法についてもかなりお世話になっているはずです。
一番難しいのは:ですが、これも「右側や下側を大きな塊として捉える」文法なのは見て分かると思います。これを書くことによって、括弧を大きく減らすことができます。Haskellの$やOCamlの@@、Pythonの:なんかでお世話になったあの記法です。構文において括弧の対応は非常に読みづらいことで知られており、括弧の対応を色で表すエディタ補完がたくさんあります。これをなくせればまさしく「軽量」と言ってよいのではないでしょうか。
改行による全ての分割
区切りの後に,をつけ忘れた経験はないでしょうか? しかし、Pythonでは行末に;を付けなくてもよいことを考えれば、
[
1
2
3
4
] // => [1,2,3,4]
と書いて良いはずです。
インデントによる式の継続
説明はほとんど要らないでしょう。
my f() = sub:
return
1 + 2 + 3 + 4
はきちんと10を返します。これで;区切り問題も見た目上は解決です。
変数宣言とスコープ
変数宣言にはmy, our, pubがあり、それぞれブロックスコープ、モジュールスコープ、パッケージスコープになっています。これは単純に(なにもなし)、pub(crate)、pubとRustで書いていてつらくなったので導入してあります。
まとめ
Yulangが目指しているのは、AE/Hを表に出した実験言語ではありません。return、for、名前付き引数、メソッド呼び出しのような普通のLLの書き味を保ったまま、その裏側を型付きeffect、handler、role、型推論で組み立て直すことです。
そうすると、言語本体だけが持っていた制御構文の作り方を、ライブラリ側にも開けます。普通の言語に見えるまま、普通の言語を拡張できる。それがYulangで一番やりたいことです。
Discussion