👀

effectで「普通のプログラミング言語」を作る:Yulangの設計思想

に公開

Yulangという新しい言語を作りました。Playgroundが既に動く状態で開発を継続中です。

要約

Yulangは「AE/Hを搭載した言語」というより、「AE/Hで普通のスクリプト言語(LL, Lightweight Language)を作る」試みです。

ユーザーが普段見るのは returnforguard:.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言語と違うのか」という点です。その思想は「エフェクトは舞台装置であって主役ではない」「構文をできるだけ軽く保つ」ことです。

エフェクトは舞台装置であって主役ではない

舞台装置としてのエフェクト

subreturnの話をしましたが、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を表に出した実験言語ではありません。returnfor、名前付き引数、メソッド呼び出しのような普通のLLの書き味を保ったまま、その裏側を型付きeffect、handler、role、型推論で組み立て直すことです。

そうすると、言語本体だけが持っていた制御構文の作り方を、ライブラリ側にも開けます。普通の言語に見えるまま、普通の言語を拡張できる。それがYulangで一番やりたいことです。

Discussion