演算子宣言
Yulang の演算子は、通常の export された binding と parser table への構文追加を組み合わせた定義である。 下流のファイルは、その演算子構文を import した後でなければ、その演算子を parse できない。
Fixity
pub nullfix(return) = std::control::flow::sub::return ()
pub prefix(return) 1.0.0 = std::control::flow::sub::return
pub prefix(not) 8.0.0 = std::bool::not
pub infix (+) 5.0.0 5.0.1 = \x -> \y -> x.add y
pub suffix (..) 8.0.0 = std::data::range::from_included| Fixity | 使用例 | メモ |
|---|---|---|
nullfix | return | オペランドを取らないキーワード風演算子 |
prefix | not x、return x、fail e | 右オペランド 1 つ |
infix | x + y、xs ++ ys | 2 オペランド。左右の binding power を持つ |
suffix | 0..、x? | 左オペランド 1 つ |
prefix(...) / infix (...) の名前部分は、記号名のときは丸括弧で囲む。 not / return のような通常の識別子もそのまま丸括弧に入れる。
binding power
binding power は 5.0.0 のような dot 区切りの 10 進数で書く。 数が大きいほど強く結合する。 infix 演算子は <left>.<right> のペアを持ち、真ん中で非対称にすると結合方向を決められる。
5.0.0 5.0.1:レベル 5 で左結合(+/-の標準)4.0.0 4.0.0:標準の range 演算子が使う binding power
prelude は次の binding power を使う。
| 演算子 | binding |
|---|---|
or | 1.0.0(lazy) |
and | 2.0.0(lazy) |
==、!=、<、<=、>、>= | 3.0.0 |
..、..<、<..、<..< | 4.0.0 |
+、- | 5.0.0 |
*、/ | 6.0.0 |
not(prefix) | 8.0.0 |
ユーザコードに新しい演算子を入れるときは、prelude のレベルと衝突せず、間に収まるように選ぶ。
Lazy 演算子
lazy infix の body は 両方 のオペランドを thunk(() -> value)として受け取る。 必要な側だけ force すればよく、a and b のような呼び出し側で thunk 化を意識する必要はない。 prelude の and / or はこの仕組みで短絡評価を実現する。
pub lazy infix(and) 2.0.0 2.0.1 = \a -> \b ->
if a():
b()
else:
false
pub lazy infix(or) 1.0.0 1.0.1 = \a -> \b ->
if a():
true
else:
b()演算子を関数として呼ぶ
演算子定義の右辺は普通の binding なので、path 経由で関数として呼べる。
1 + 2
std::int::add 1 2 // 明示形(あまり使わない)
(1).add 2 // role method 経由+ 自体は std::core::ops の中で \x -> \y -> x.add y として定義されているので、underlying な Add role method x.add y を直接呼ぶ形が、演算子を第一級参照する最も近い書き方になる。
演算子の実装を第一級参照したいときに役立つ。
Import
use std::core::ops::*
use my_ops::(+)
use my_ops::* without (+), debug演算子構文は丸ごと import、名前指定で import(記号演算子は括弧で囲む)、glob から without で除外、のいずれもできる。 parser が後続の式を読む前に演算子定義を知る必要があるので、これは通常の値 import 以上に重要。
新しい演算子を定義する
pub infix (++) 4.0.0 4.0.0 = \xs -> \ys -> std::data::list::append xs ys
[1, 2] ++ [3, 4] // [1, 2, 3, 4]右辺は通常の curried 関数である。 優先順位の階層の中で適切な binding power を選ぶ。
落とし穴
- 記号演算子は最初の使用前に import する必要がある。順序が逆だと、name 解決ではなく parse エラーとして弾かれる。
pub prefix(name) ...の宣言とuse foo::(+)のような import は、両方とも 構文をスコープに持ち込む。値だけのパス import は構文を持ってこない。- 2 つの glob import が同じ演算子名を出してくると衝突する。
withoutで片方 を除外して曖昧さを解消する。
関連ページ
- application と演算子:parse された演算子と裸 application の関係
- 構文スタイル:記号まわりの空白ルール
std::core::ops:prelude の演算子定義