MEP 26: ユーザー定義関数
- Created: 2025-08-16 12:10:00
- Updated: 2025-08-17 20:20:00
- Status: Supported(v1.0.03)
モチベーション
MFGはテンソルという単位があるため他の言語ほど優先度は高くないが、 hashなどの演算でユーザー定義関数が欲しい事はある。
提案
以下のようなシンタックスで関数を定義出来るような提案。
fn alpha_blend |src1:i32, src2:i32, alpha:f32| {
# シンタックスを考えるためなのでコードは適当
i32( src1*alpha+src2*(1-alpha) )
}
- 関数定義は
fnキーワード - 引数はコロンで区切って型のアノテーションをつける。
- returnの型は明示しない
基本的にはテンソル定義やrsumなどで使うブロックのシンタックスに、仮引数の型アノテーションを追加したものとなっている。
引数無しの場合は以下のどちらでも良い。(検討の議論を参照)
fn argless_func || { 1*2 }
fn argless_func | | { 1*2 }
また、最初の段階では引数や型は限定したものにしておきたい。 具体的には以下の引数やreturn型はサポートしない。
- genType
- タプル
- テンソル
ベクトル型は対応する。 ベクトル型はv2〜v4のみ対応。
fn hash |v:u32v4| {
v.x*10u+v.y
}
検討
仕様について議論が必要な事や検討すべき事柄。
キーワードはfnなのか?
テンソルではdefを用いているのに関数はfnなのは一貫性が無いかもしれない。defunの方が一貫性はあるが長い。 func、funなども候補となる。
関数のシンタックス的には一番Rustに似ていること、短さを優先するMFGとしては一番短いfnを提案としている。
Rust
fnはRustのキーワード。ブロックのシンタックスが一番似ているのがRustなので、これに揃えるのがいいか。
関数 - The Rust Programming Language 日本語版
Go
golangはfunc。
Swift
Swiftはfunc。
Python
Pythonはdef。
4. More Control Flow Tools — Python 3.13.7 documentation
Ruby
Rubyもdef。
クラス/メソッドの定義 (Ruby 3.4 リファレンスマニュアル)
引数無しの場合のトークン
||はトークナイザのレベルで一つのトークンにされてしまう。 一方 | | は2つのトークンになる。
パースとしてはどちらかに統一したい気もするが、ほとんどの場合空白無しにしておきたいけれど、 引数を削除していった時などに空白があるとパースエラーなのは納得しがたいので、 両方を明示的にサポートするとする。
副作用が無いので引数無しの関数は用途は無いはずだが、シンタックスは決めておく。
将来的なsamplerのユーザー定義の可能性
機能としては、samplerもユーザー定義したい項目となる。 この時に一貫性を考える必要はあるかもしれない。
例えばClampToBorderValueのようなものを定義出来るようにするには以下のようなシンタックスが考えられる。
defsam clamp_to_border_value<ts: [2, i32]> |defval: i32| |arg1: i32, arg2:i32| {
# コードは適当
ifel( ts.extent(0) <= arg1 || ts.extent(1) <= arg2,
defval,
ts(arg1, arg2)
)
}
tsの型指定や2種類の引数のためのシンタックスなど、考えなくてはならない事は多いのでこのシンタックスになる事は無いだろうが、 defsamの所のキーワードとの一貫性は現時点でも考えられる所。
fnとの対応でいけばsmpとかsampとかになるが、これはさすがに定義とわかりにくいので避けたい。
defsamと対応するならdefunの方が適切という事にもなる。
だがサンプラー定義は出来たら嬉しいのは間違いないが、本当に実装するかは未定なので、そのために関数定義のキーワードが影響を受けるべきなのか?
戻りの型指定はいらないのか?
Rustなどでは戻りの型を書く。これで定義のパースの段階で関数の型が確定する。
一方、MFGでは最後の式が戻りの型になるのはテンソルなどのブロックでも同様なので、 一貫性という点で同じように戻りの型の指定は無しというのを提案としている。
制御構造が式をメインとしていてreturn文などが無いため、 戻りの型が複雑になる事も無いので意図したのと違う戻りの型になっている、 という事はほとんど無い。
一方、関数リファレンスのようなものをサポートする時には結局戻りの型の指定というのは必要になりうるので、 多くの言語ではそれと同じシンタックスとして関数定義の型も指定するようになっている。
MFGで関数リファレンスのようなものをサポートする事があるかは未定だが、 少なくともletでは使い道が無く、MFGではvarの使用は極めて強い制限の元でしか使えないのが現状となっているので、 他の言語とは大きく状況が異なるのは確か。
サポートしない型について
タプルは型の表記が存在していないため、一旦サポートを見送りたい。テンソルも同様。 テンソルは戻りだけでは無く次元も必要なため記述がより困難となる。
基本的にはあとから自然に追加出来てそれ以前のシンタックスの変更が無い要素に関しては後回しにしたい。 現時点でのユースケースはhash関数のようなものをユーザーが定義出来る事なので、 まずはそれを実現する最も小さなサブセットから始めたい。
タプルはサポートしないが、ベクトルは現時点でもf32v2などの表記で確定している(paramがこの表記になっているため)ので、 サポートした。
ただしv5より大きな次元は他の所での使用例が無いため、動くか不安なのでひとまず4次元までとする。 また、1次元ベクトルはサポートしない。(つまりv2〜v4でv1はサポートしない)
実装について
現時点でも内部的にはインライン関数がサポートされているので、 ユーザー定義関数はこれと同じ仕組みになる想定。
インライン関数なので最終的には呼び出し引数の完全な情報がある。(例えばテンソルのextentなども参照出来るので理論上はサポート出来るはず)
インライン関数は一意の名前の内部変数が生成されて、それへの代入として実現されている。
内部的にはgenTypeもサポートされているが、シンタックスが煩雑になるので最初の時点ではサポートしない。