例: mandelbrot
import "sys" のプリミティブを使って Mandelbrot 集合 を描画するプログラムです。すべてのロジックが 2-cell の合成として記述されています。
全体コード
import "sys"
fdup = val.dup[f32]
square :=
val.dup[f32x2];
f32x2.unpack f32x2.unpack;
(fdup; f32.mul) (fdup; f32.mul) (f32.lit[2] f32.mul; f32.mul);
f32.sub f32;
f32x2.pack
step: f32x2 f32x2 → f32x2 f32x2 =
square val.dup[f32x2]; f32x2.add f32x2
rep4[x: C → C, f: x → x]: x → x = f; f; f; f
step4 := rep4[f32x2 f32x2, step]
step16 := rep4[f32x2 f32x2, step4]
step64 := rep4[f32x2 f32x2, step16]
coloring: f32x2 → f32 := f32x2.unpack;
(fdup; f32.mul) (fdup; f32.mul);
f32.add f32.lit[2];
f32.le;
f32.lit[1] f32.lit[0] bool;
bool.ind[f32]
result = f32x2.lit[0,0] (f32x2 f32x2.lit[0.6,0.5]; f32x2.sub; f32.lit[3] f32x2; f32x2.scale);
step64;
f32x2 val.drop[f32x2];
coloring
解説
前提: sys ライブラリ
import "sys" により、以下のプリミティブが使えます:
f32,f32x2— 浮動小数点型。すべてC → Cの 1-cellf32.add,f32.mul,f32.sub— 算術演算(2つのf32を受け取りf32を返す 2-cell)f32x2.pack/f32x2.unpack—f32 f32 ↔ f32x2の変換val.dup[x]—x → x x(値の複製)val.drop[x]—x → C(値の破棄)
Donut では関数は 2-cell の合成であり、すべてのデータは 1-cell の「ワイヤー」として流れます。
fdup: f32 の複製
fdup = val.dup[f32]
f32 → f32 f32。1つの f32 値を複製して2つにします。頻出するのでエイリアスしています。
square: 複素数の二乗
square :=
val.dup[f32x2];
f32x2.unpack f32x2.unpack;
(fdup; f32.mul) (fdup; f32.mul) (f32.lit[2] f32.mul; f32.mul);
f32.sub f32;
f32x2.pack
型は f32x2 → f32x2。複素数 (z = a + bi) の二乗 (z^2 = (a^2 - b^2) + 2abi) を計算します。
データフローを追うと:
val.dup[f32x2]— 入力(a, b)を複製:(a, b) (a, b)f32x2.unpack f32x2.unpack— 展開:a b a b(fdup; f32.mul) (fdup; f32.mul) (f32.lit[2] f32.mul; f32.mul)- 最初の2つ:
aを複製してa*a、bを複製してb*b - 3つ目: 定数
2とaとbを掛けて2*a*b - 結果:
a² b² 2ab
- 最初の2つ:
f32.sub f32—a² - b²と2abf32x2.pack—(a² - b², 2ab)にパック
:= で定義しているので、square は独立したセルとして宣言されつつ、評価時にはこの合成として計算されます。
step: Mandelbrot の反復ステップ
step: f32x2 f32x2 → f32x2 f32x2 =
square val.dup[f32x2]; f32x2.add f32x2
型は f32x2 f32x2 → f32x2 f32x2。入力は (z, c) で、(z \leftarrow z^2 + c) を計算して (z', c) を返します。
square val.dup[f32x2]—zにsquareを適用してz²にし、cを複製:z² c cf32x2.add f32x2—z² + cとc:(z²+c, c)
c を保持したまま z を更新するので、繰り返し適用できます。
rep4 と反復
rep4[x: C → C, f: x → x]: x → x = f; f; f; f
step4 := rep4[f32x2 f32x2, step]
step16 := rep4[f32x2 f32x2, step4]
step64 := rep4[f32x2 f32x2, step16]
rep4 はパラメトリックな定義で、任意の 2-cell f: x → x を4回繰り返します。
step4= step を 4 回 = 4 回反復step16= step4 を 4 回 = 16 回反復step64= step16 を 4 回 = 64 回反復
:= で定義しているので、それぞれ独立したセルとして扱いつつ、評価時には展開されます。
coloring: 発散判定
coloring: f32x2 → f32 := f32x2.unpack;
(fdup; f32.mul) (fdup; f32.mul);
f32.add f32.lit[2];
f32.le;
f32.lit[1] f32.lit[0] bool;
bool.ind[f32]
型は f32x2 → f32。最終的な z の値から色を決定します。
f32x2.unpack—(a, b)をa bに展開(fdup; f32.mul) (fdup; f32.mul)—a²とb²を計算f32.add f32.lit[2]—a² + b²と定数2f32.le—a² + b² ≤ 2→boolf32.lit[1] f32.lit[0] bool— 定数1、定数0、判定結果を並べるbool.ind[f32]—boolの値で分岐。真なら1(集合内)、偽なら0(発散)
result: 全体の組み立て
result = f32x2.lit[0,0] (f32x2 f32x2.lit[0.6,0.5]; f32x2.sub; f32.lit[3] f32x2; f32x2.scale);
step64;
f32x2 val.drop[f32x2];
coloring
型は f32x2 → f32。ピクセル座標 f32x2(入力)を受け取り、色 f32 を返します。
- 初期値の準備:
z = (0, 0)とcを用意。cは入力座標を(0.6, 0.5)を中心に、スケール3で変換したもの step64— 64 回反復f32x2 val.drop[f32x2]—cを捨ててzだけ残すcoloring— 発散判定して色に変換
ポイント
- すべてのロジックが 2-cell の合成 で表現されている
- 分岐(if/else)は
bool.indで実現 - ループは
rep4のパラメトリック定義を入れ子にして実現 :=(定義)を使うことで、中間セルに名前を付けつつ評価時は展開