llrlの言語の概要
以下はシンタックスハイライトを効かせるため拡張子を .llrl.lisp にしている。
S式
llrlは見た目はけっこうLispである。 let, begin, if といった式レベルの構文はLispの命名をおおむね採用している。
code:hello.llrl.lisp
(println! (string/join ", " (array hello world))))
関数
code:function.llrl.lisp
(function (square x) (* x x))
(println! (square 11))
(println! (even? 123))
(println! (odd? 123)))
関数宣言は function 。 let によるクロージャもサポートしている。
関数の型
型システムは古典的なHindley-Milner 型推論を実装している。Hindley-Milner 型推論は、
OCaml, Haskell等で使われていてよく枯れている
トップレベルも含めてほとんど型注釈しなくてもうまく推論が効くので、「Lispっぽさ」を出すのに相性が良い
例えば上の関数 square は、以下のように型注釈を付けられる。
code:function-with-type-signature.llrl.lisp
; 推論される型と同等: (Mul A) を満たす任意の型Aの値を1つ取り、Aを返す
(function (square3 x) {(forall A) (-> A A) (where (Mul A))}
(* x x))
; 32bit 符号あり整数に限定する場合
(function (square2 x) {(-> I32 I32)}
(* x x))
モジュール
各llrlソースコードはそれぞれモジュールとなる。モジュール間でも定義を import export で共有できる。
code:import-export.llrl.lisp
; foo.llrl
(export hello)
(function (hello) (println! "Hello"))
; bar.llrl
(import "~/foo" hello)
(hello)
import/exportの対象の指定には、任意の識別子にマッチするワイルドカード _ を前置・後置することができる。Lisp系言語の識別子に使える文字の自由さと合わせて、 ord-map/から始まる定義をexportする、といった使い方ができる。 モジュールの循環importは出来ない。import/exportの不動点を見つけたりマクロの実装が大変/困難なため。
代数的データ型とパターンマッチング
Haskell等でお馴染みの代数的データ型を宣言できる。
code:data.llrl.lisp
(data (Tree A)
(tree:branch (Tree A) (Tree A))
(tree:leef A))
match 式でパターンマッチングを行える。
code:data.llrl.lisp
(function (sum a)
(match a
(function testdata
(tree:branch (tree:branch (tree:leef 2)
(tree:branch (tree:leef 3)
(tree:leef 4)))
(tree:branch (tree:leef 5)
(tree:leef 6))))
(assert-eq? (sum testdata) 20)
型クラス
Rustではトレイトと呼ばれているアドホック多相のための言語機能。llrlはどちらかというと関数型言語寄りなので暗黙の Self は含まない。複数のパラメタを持てるが、HaskellでいうTypeFamilies, あるいはFunctionalDependenciesあたりの機能を実装できていないので型推論が弱い。
インスタンス宣言は、インスタンスの明示的な指定は (今のところ) 出来ないが、import/exportの有無でスコープを制限できる。
マクロ
マクロは単に (-> (Syntax Sexp) (Result (Syntax Sexp) String)) 型の関数として定義される。例えば標準ライブラリの lambdda マクロ (5-lambda.llrl) は、 code:lambda.llrl.lisp
(macro (lambda s)
(s/match s ; 引数 (Syntax Sexp) の構造とマッチ
[(_ ,args ,@body) ; (lambda args body ...) の形式のS式を展開
(ok
,tmp-f)))] ; クロージャを結果とする
[_
(err "Expected (lambda (arg ...) body ...)")]))
let によるローカル関数宣言に変換される。
llrlではHindley-milner 型システムの範疇を超えてそうなコードはだいたいマクロで定義されている。特に可変長引数っぽいものは全てマクロである。llrlは見た目はLispだが、マクロ展開されたコードの実際は (型クラスが加わった) OCamlに近い。
糖衣構文
table:syntax-sugar
式 脱糖後の式 対応する言語機能 備考
'expr (quote expr) クォート構文 Lispと同様
`expr (quasiquote expr) quasiquoteマクロの呼び出し std/boot/3-quasiquoteを参照
,expr (unquote expr) quasiquoteマクロ用マーカー
,@expr (unquote-splicing expr) quasiquoteマクロ用マーカー
~expr (load expr) load関数の呼び出し OCamlの ! 相当、std/accessを参照
\expr (capture expr) 使用のキャプチャ構文 実例はstd/boolなどのマクロ定義を参照
(...)? (try? (...)) try?マクロの呼び出し Option型のearly return、std/boot/1-tryを参照
(...)! (try! (...)) try!マクロの呼び出し Result型のearly return、std/boot/1-tryを参照
expr {...} (annotate expr ...) 型注釈構文
a... @ b... a... (b...) リスト (ネストを軽減できる) トップレベルでは使用不可