ツアー
Yulang の主要機能を短く巡るページです。すべての例は Playground で実行できます。
「最初に動かす」「型 + 効果」「データと振る舞い」「制御フロー」「エラー」の順で見ていきます。
基本
1 + 2トップレベルに式を書くと、その値が実行結果に表示されます。スクリプト言語みたいに「いきなり書いていきなり走る」感覚です。
my double x = x + x
double 21my f x = ... は「名前と引数を 1 行で並べる」関数 binding です。OCaml の let f x = ... や Haskell の f x = ... に近い書き方になります。複数引数なら my add x y = x + y のように並べます。
可視性は次の通り。
my— private(同じ scope 内だけ)our— companion module に公開pub— 外部 module へ export
構造体
struct point { x: int, y: int } with:
our p.norm2 = p.x * p.x + p.y * p.y
point { x: 3, y: 4 } .norm2struct は nominal な record type です。Rust や OCaml の record と似ています。違うのは with: で method を一緒に書ける こと。our p.norm2 の p は receiver 名で、point の値から .norm2 として呼べます。class を作るほどではないけれど「型に紐付いた振る舞いを束ねたい」場面に向いています。
省略可能引数
record pattern の default は、named optional argument として使えます。
my area {width = 1, height = 2} = width * height
area { width: 3 }
area {}
area { width: 3, height: 4 }default は左から右へ評価され、前の field を参照できます。
my f {a = 1, b = a + 1, c = b + 1} = (a, b, c)
f {} // (1, 2, 3)
f { a: 10 } // (10, 11, 12)Python のキーワード引数 + default、Ruby のハッシュ展開、TypeScript の function f({a = 1} = {}) に近い使い心地です。型はちゃんと推論されるので、注釈は要りません。
可変 binding と参照
my $x = ... は可変 binding を作ります。$x は読み取り、&x = v は書き込みです。
my $x = 10
&x = $x + 1
$x$ と & が見た目に出るのは、Perl / Raku 風です。「ふつうの binding は immutable、可変にしたいときだけ印を付ける」スタイルで、コードを読むときに「ここから状態がある」と一目で分かります。
field や index にもそのまま使えます。
my $xs = [2, 3, 4]
&xs[1] = 6
$xs内部的にはこれらは小さな var effect として展開されるので、関数を抜けない可変状態として型に乗ります。「グローバルに見える変数」ではなく「読み書きの場所をその場で開いている」というニュアンスです。
非決定性
std::undet の each は「選ぶ」を 1 行で書けます。.list はすべての結果を集めます。
(each [1, 2, 3] + each [4, 5, 6]).listこれは「[1,2,3] から 1 つ、[4,5,6] から 1 つ選んで足す。可能な組み合わせ全部」を表します。Prolog や Haskell の list monad、SQL の cross join のような考え方を、ふつうの式に埋め込めるイメージです。
無限範囲も扱えます。前の選択を使って後の範囲を絞れば、.once で最初に成功する組を返します。
{
my a = each 1..
my b = each a<..
my c = each b<..
guard: a * a + b * b == c * c
(a, b, c)
} .onceこれは「ピタゴラス三角形を 1 個欲しい」と宣言したらそれを探してくる、というプログラムです。
Junction
all と any は collection 全体に比較を持ち上げます。
if all [1, 2, 3] < any [2, 3, 4]:
1
else:
0ふつうの言語なら [1,2,3].all? { |x| [2,3,4].any? { |y| x < y } } のように畳まないと書けないものを、if の中にそのまま書けます。if が effectful な条件を受け取れるように設計されているおかげです。
エフェクト
act は effect interface を宣言します。operation は普通の関数のように呼び、catch で処理します。
act console:
our read: () -> int
our ask() = console::read()
our run_console(action: [console] 'a): 'a = catch action:
console::read(), k -> run_console(k 42)
run_console:
ask()ask() の型は [console] int で、これは「int を返すが console effect を起こすかもしれない計算」を意味します。run_console は catch でその operation を処理し、戻り値の型から [console] を取り除きます。
handler arm の k は continuation で、k value を呼ぶと operation の呼び出し位置へ値を返して計算を再開します。テスト用に handler を差し替えたり、loop / 早期 return / 例外を全部この仕組みで扱えるのが嬉しいところです。
ループと早期 return
for x in xs: は Fold を実装する値を走査します。list や range は標準で対応しています。sub: は早期 return scope を作り、return value が直近の sub: から抜けます。
sub:
for x in 0..:
if x == 5: return x
0return は parser 専用 keyword ではなく、prelude が sub effect の return operation を演算子として export しています。last / next / redo も同じ仕組みで、loop の effect を呼んでいるだけです。「早期脱出」「break」「continue」が、特殊構文ではなく 同じ effect 機構の応用 で書かれています。
エラー
error は enum と effect operation 群を同時に作る糖衣です。
error fs_err:
not_found path
denied path
invalid_path pathfs_err::not_found "path" は、文脈によって data constructor としても、throwing operation としても読めます。
my err: fs_err = fs_err::not_found "/x" // 値
fs_err::not_found "/x" // [fs_err] effect を起こす別の error へまとめる場合は from を使います。
error io_err:
fs from fs_errエラーは effect row の中に名前で残るので、「何が起きうるか」が型を見ればわかります。anyhow のように消える Display ラッパーは意図的に持たず、必要なら wrap で result 値に閉じてから扱います。
コメント
// 通常の line comment
-- doc comment
---
複数行の doc comment
----- は普通のコメントではなく doc comment なので、ただのメモには // を使います。