式とベクトライズ演算
MFGにおいては、多くの構成要素は式となります。 他の言語では文になることが多いループや条件選択も式として実装されています。
式とは、letの右辺に来る事が出来るもので、何らかの値となり、それに対する型が決まっているものです。
ここでは式のうち、基本的な二項演算などの算術式と関数呼び出しについて見ていきます。
算術演算
通常の足し算や引き算など。単項演算子と二項演算子がある。
単項演算
算術演算の単項演算には以下の2つがある。
| 演算子 | 説明 |
|---|---|
- | マイナス |
! | 論理 not |
例:
let b = -a
let c = !a
算術演算以外の単項演算子としてはsplat演算子 * があるが、 これはその他の単項演算子と異なり、特別な場所でしか使えないため、 のちほど改めて説明します。
二項演算と優先順位
二項演算は以下のものがある。 優先順位の一番低いものから順に以下。
| 演算子 | 説明 |
|---|---|
|| | 論理OR |
&& | 論理AND |
==, !=, < <= > >= |> | 比較演算、パイプ演算子 |
| | ビットOR |
& | ビットAND |
>>, << | シフト演算子 |
+, - | 足し算、引き算 |
*, /, % | 掛け算、割り算、モジュロ演算 |
^ | 累乗(右結合) |
累乗だけ右結合です。
また、^を累乗に使っているため、排他的論理和は二項演算ではなく関数になっています。 xorという関数です。詳細は組み込み関数を参照ください。
二項演算のベクトライズ
タプルには、ある演算を各要素に適用してその結果をタプルとする、という機能があります。 これをベクトライズと呼びます(ややこしいですが、ベクトルとは別の機能です)。
いくつかの単一引数関数、ifel、キャスト、二項演算がベクトライズをサポートしています。 ここでは二項演算のベクトライズについて扱います。
以下のa1とa2も同じ意味となります。
let tup1 = [1.0, 2]
let tup2 = [3.0, 4]
# 要素ごとに足す
let a1 = [tup1.x+tup2.x, tup1.y+tup2.y]
# ベクトライズ
let a2 = tup1+tup2
tup1のxはf32型、yはi32型な事に注意してください。 ベクトライズはtup1とtup2の型が揃ってさえいれば、 つまり要素ごとに二項演算をする時の左右が一致していれば、 tup1.xとtup1.yの型が違っていても行う事が出来ます。
なお、2項演算の結果はまたタプルになるので、これをつなぎ合わせる事が出来ます。
let tup1 = [1.0, 2.0]
let tup2 = [3.0, 4.0]
let tup3 = [5.0, 5.0]
let s = (tup+tup2)/tup3
数学のベクトルの演算のような事が出来ます。
等価判定の == などもベクトライズで使う事が出来ます。
let tup1 = [1, 2, 1]
let tup2 = [3, 2, 1]
# sは[0, 1, 1]になる
let s = tup1 == tup2
厳密にはsの要素が1になるかは環境による所なので、[0, 非ゼロ, 非ゼロ] になる、 というのが正しい表現になります。
これは後の章で出てくるifelのベクトライズと合わせる事で、複数の要素を一度に計算出来ます。
スカラーとタプルの2項演算
2項演算は、スカラーとタプルの組み合わせもサポートしています。 この場合はブロードキャストといって、タプルの個々の要素に対してスカラーの演算がされたのと同じ結果になります。
例えば、以下の3つは同じ結果となります。
let tup = [3.0, 4.0]
let m1 = [tup.x*2.0, tup.y*2.0]
let m2 = 2.0*tup
let m3 = tup*2.0
掛け算は数学のスカラーとベクトルと同じになりますが、足し算でも同じ事が出来ます。 以下のa1とa2とa3は同じ値になります。
let tup = [3.0, 4.0]
let a1 = 5.0+tup
let a2 = [5.0, 5.0] + [3.0, 4.0]
let a3 = [3.0+5.0, 4.0+5.0]
splat演算子
特別な単項演算子として、splat演算子、*というものがあります。 これは以下の場所でしか使う事の出来ない演算子です。
- タプルのリテラルの中
- 関数呼び出しの引数の中
- テンソルやサンプラーの参照の引数の中
これらの中で、何かのタプルの前に*を置くと、 そのタプルを展開した要素を並べたもの、とみなす、というのがsplat演算子です。
テンソルの例:
let tup = [10, 20]
# input_u8(10, 20)と同じ意味
input_u8(*tup)
タプルのリテラルの例:
let red = [0, 0, 0xff]
# let bgra = [0, 0, 0xff, 0x88]と同じ意味
let bgra = [*red, 0x88]
関数呼び出しでの例:
let a = 0.7
let b = 0.6
let ratio = 0.3
let tup = [a, b, ratio]
# mix(a, b, ratio)と同じ意味
mix(*tup)
関数呼び出し
関数呼び出しも、式の代表的なものです。
関数は関数名のあとにカッコをつけて、その中に引数を入れる事で呼び出せます。
例えば sin(1.5) などが関数呼び出しです。
関数呼び出しのベクトライズ
三角関数などの単一引数の関数は、二項演算子と同様にベクトライズして使う事が出来ます。 以下のs1とs2は同じ意味になります。
let tup = [1.0, 2.0]
# 要素ごとのsin
let s1 = [sin(tup.x), sin(tup.y)]
# sinのベクトライズ呼び出し
let s2 = sin(tup)
どの関数がベクトライズ呼び出しとして使えるかは個々の関数のドキュメントを見る必要がありますが、 引数が一つの通常の数学の計算をするような関数はだいたいベクトライズ呼び出しとして使う事が出来ます。
関数呼び出しとsplat演算子
関数呼び出しではsplat演算子が使えます。 詳細は前述の「splat演算子」の項を参照ください。
...による最後の引数の特別扱い(Trailing Expression)
関数の最後の引数を ... にすると、その呼出しの次の式を最後の引数だとみなす、 というシンタックスシュガーがあります。
# let d = distance( [1.2, 2.3], [4.5, 3.2]) と同じ意味。
let d = distance( [1.2, 2.3], ...) [4.5, 3.2]
関数では使い道の無い機能ではありますが、ifelでネストを避ける為に用いられます。 詳細はifelとループを参照ください。
パイプ演算子と...によるパイプライニング
パイプ演算子 |> を使うと...がどこの式と置き換わるかを変更する事が出来ます。 二項演算子である |> は、左辺の式で右辺の ... を置き換えます。
つまり以下の3つは同じ意味になります。
f(a, b, c)
f(a, b, ...) c
c |> f(a, b, ...)
この機能を使って、パイプライニング出来ます。
let col = input_u8(x, y)
|> to_ncolor(...)
|> gamma2linearA(...)
MFGでは改行は式の終わりと認識されますが、次の行の行頭がパイプ演算子のときだけは特別扱いされるため、 上記の例のように行頭にパイプ記号を書いても問題ありません。
名前付き引数
一部の関数は名前付き引数の引数があります。 例えば以下のreduceのinitなどがそれに当たります。
reduce(init=[0, 0], 0..<10) | index, accm | {
let [a, b] = accm
[a+index, b+index*2]
}
名前付き引数は順番を気にせずに使う事が出来ます。
名前付き引数はそれを提供している関数でのみ使う事が出来ます。 引数が多い関数でそれぞれの引数の意味が分かりにくいものに名前付き引数を提供しています。
名前付き引数を提供しているかどうかはおのおのの関数の解説を参照ください。 なお、名前付き引数と名前無しの引数が併用されている場合は、 まず名前付き引数の引数が埋められたあとに、残りの引数を順番に指定した、とみなされます。
名前付き引数を名前無しで引数の順番で指定する事も出来ますが、将来の仕様変更で順番が変わる可能性があるものが多い為、 名前付き引数が使用出来る所は極力名前付き引数を使用してください。
名前付き引数に関してはサンプラーやtransとreduceでより詳細に扱います。
ブロック引数
多くのループ系の機能を提供する関数は、最後にブロックを引数に取ります。 最後のブロックはカッコの直後に置きます。
rsum(0..<3) |i| { (i+3)*2 }
最後の|i| { (i+3)*2 }がブロック引数となり、このブロックがrsumに渡されます。 ブロック引数についてはifelとループのループ系の機能を扱う所で扱います。
そのほかの式
MFGはプログラムのほとんどの要素が式となるので、このリファレンスの多くの場所で、このページでは扱わなかった式の話があります。
- テンソル呼び出しはテンソル
- sampler呼び出しはサンプラー
- タプルのswizzle演算子などは型
- reduce, rsum, ifelなどはifelとループ
- 範囲を表すRangeについてもここ
- tensor reduceも式となる事があるが、これはtransとreduce