計算の書き方
import "sys" を使った具体的な計算の書き方を説明します。
ワイヤーの束
Donut の計算では、データは ワイヤー(1-cell)の束として流れます(donut-app では上から下に描画されます)。計算のどの時点でも「今、手元にどんなワイヤーがあるか」を意識します。
import "sys"
// f32.lit[3] は「何もないところから f32 を1本生やす」
// C → f32
three = f32.lit[3]
ここで C は「ワイヤーが0本」の状態です。f32.lit[3] は 0本の状態から f32 ワイヤーを1本作ります。
1次合成(;)
; で2つの操作を繋ぐと、前の出力が次の入力になります。
// f32.lit[3]; f32.neg
// C → f32 → f32
// = -3
neg_three = f32.lit[3]; f32.neg
f32.lit[3] が C → f32、f32.neg が f32 → f32 なので、繋ぐと C → f32 になります。
0次合成(スペース)
スペースで2つの操作を並べると、別々のワイヤーに対して同時に動作します。
// f32.lit[1] f32.lit[2]
// C → f32 f32
// 手元に f32 が2本ある状態
pair = f32.lit[1] f32.lit[2]
この2本のワイヤーを f32.add に渡すと:
// f32.lit[1] f32.lit[2]; f32.add
// C → f32 f32 → f32
// = 3
sum = f32.lit[1] f32.lit[2]; f32.add
ワイヤーの管理
通常のプログラミング言語では変数に名前を付けて自由に何度でも参照できます。Donut ではそうではなく、ワイヤーの束を明示的に操作します。
複製: val.dup
値を2回使いたい場合、明示的に複製します。
// val.dup[f32]: f32 → f32 f32
// 1本のワイヤーを2本に分ける
// x² を計算する: x を複製して掛ける
square: f32 → f32 = val.dup[f32]; f32.mul
破棄: val.drop
使わないワイヤーは明示的に捨てます。
// val.drop[f32]: f32 → C
// ワイヤーを1本消す
// 2本のうち最初だけ残す
first: f32 f32 → f32 = f32 val.drop[f32]
f32 val.drop[f32] は「最初の f32 はそのまま通し、2番目の f32 を捨てる」という並列合成です。
入れ替え: val.swap
ワイヤーの順番を入れ替えます。
// val.swap[f32, u32]: f32 u32 → u32 f32
考え方: 手元のワイヤーを追う
計算を書くときは、各ステップで「今ワイヤーが何本、どんな型で並んでいるか」を追います。
例: (a + b) * (a - b) を計算する関数 f32 f32 → f32
// 入力: a b (f32 が2本)
diff_of_squares: f32 f32 → f32 =
val.dup[f32] val.dup[f32]; // a a b b
f32 val.swap[f32, f32] f32; // a b a b
f32.add f32.sub; // (a+b) (a-b)
f32.mul // (a+b)*(a-b)
各行で手元のワイヤーがどう変化するかを追うのがコツです:
a b— 入力 (2本)val.dup[f32] val.dup[f32]— 各ワイヤーを複製:a a b b(4本)f32 val.swap[f32, f32] f32— 1本目はそのまま、中央2本を入れ替え、4本目はそのまま:a b a bf32.add f32.sub— 前2本を加算、後2本を減算:(a+b) (a-b)(2本)f32.mul— 乗算:(a+b)*(a-b)(1本)
identity と定数の組み合わせ
1-cell の名前をそのまま値として書くと、そのワイヤーを素通りさせる identity になります。これと定数生成を 0次合成すると「元の値を保ちつつ定数を追加」できます。
// x を受け取り、x と 1 のペアにする
with_one: f32 → f32 f32 = f32 f32.lit[1]
f32 は identity(f32 → f32)、f32.lit[1] は定数(C → f32)。0次合成で f32 → f32 f32 になります。
括弧 () を使うと部分式をまとめられます:
// 入力 x に対して (x², x) を作る
square_and_keep: f32 → f32 f32 = val.dup[f32]; (val.dup[f32]; f32.mul) f32
まとめ
- 計算の状態は常に「ワイヤーの束」
;で前の出力を次の入力に繋ぐ- スペースで別々のワイヤーに同時に操作する
- 値を使い回すには
val.dupで複製 - 不要な値は
val.dropで破棄 - 変数名で参照する代わりに、ワイヤーの並び順で管理する