All checks were successful
Build / build (push) Successful in 14m26s
191 lines
7.4 KiB
Markdown
191 lines
7.4 KiB
Markdown
#memo #mimium
|
||
|
||
|
||
|
||
- [[多段階計算を命令型VMインストラクションで表現したい]]
|
||
- 一時期考えていたが、あんまり筋が良くないのでやめた
|
||
|
||
|
||
[Implement multi-stage computation intepreter by tomoyanonymous · Pull Request #136 · mimium-org/mimium-rs · GitHub](https://github.com/mimium-org/mimium-rs/pull/136)
|
||
|
||
シンプルに構文木レベルでのインタプリタを別途作ることで実現できそう。
|
||
|
||
## 構文の拡張
|
||
|
||
### Type
|
||
|
||
Code Type $\langle t\rangle$を加える
|
||
$$
|
||
\begin{align}
|
||
\tau ::=
|
||
&\quad R \quad &\\
|
||
|&\quad I_n \quad &n \in \mathbb{N} \\
|
||
|&\quad \tau → \tau &\\
|
||
|&\quad \langle \tau \rangle
|
||
\end{align}
|
||
$$
|
||
|
||
### Expression
|
||
|
||
$$
|
||
\begin{align}
|
||
e ::=
|
||
&\quad R & R \in \mathbb{R} [number]&\\
|
||
|&\quad e \ e \quad& [app]&\\
|
||
|&\quad \lambda x.e& [abs]&\\
|
||
|&\quad let\; x\; =\; e\; in\; e& [let]&\\
|
||
|&\quad delay(x,e_{time},v_{bound})&[delay]&\\
|
||
|&\quad mem(e) &[mem]&\\
|
||
|&\quad feed\ x.e &[feed]&\\
|
||
|&\quad `(e) &[quote]&\\
|
||
|&\quad $(e) &[splice]&
|
||
\end{align}
|
||
$$
|
||
|
||
### Value
|
||
|
||
$$
|
||
\begin{align}
|
||
v ::=
|
||
|&\quad R & R \in \mathbb{R} [number]&\\
|
||
|&\quad cls(e,E) &[closure]&\\
|
||
|&\quad \langle v \rangle &[code]&\\
|
||
\end{align}
|
||
$$
|
||
|
||
|
||
## 課題
|
||
|
||
- 組み込みの関数の型情報がマクロ用(レベル0)とVM用(レベル1)で分かれているので、両方にあることを別々に宣言しなければならない
|
||
- 逆に、ベクターのappend・removeや文字列操作など、メモリを確保する操作はレベル0でのみ利用できる組み込み関数として制限すると、便利かもしれない
|
||
- 逆に、`self`や`delay`はレベル1でのみ利用できる関数とする
|
||
- レベル0,1両方で利用できるpersistentなモジュールは、上記2種類のいずれも使わず、かつescapeとBracketも利用しないものであればよい、ということになる
|
||
- もともとディレイの最大サイズはリテラルで指定しなければいけないという問題があったが、これを`make_delay(size:float)-> <(float,float)->float>`(レベル0で最大時間を指定すると、入力と遅延時間をとる関数のコードを返すという関数)にできるかも
|
||
- ただ、これやるとディレイが絡む関数はすべてレベル0定義になるのかな
|
||
- なんかそれよりは、[[数値プリミティブ型に常に範囲をつける]]とかのほうが筋がいいかも
|
||
|
||
```rust
|
||
fn fbdelay(max_time){
|
||
`|input,time,fb| {
|
||
(input + self*fb, time) |> make_delay!(max_time)
|
||
}
|
||
}
|
||
```
|
||
|
||
- こういうfbdelayを2個以上つかうときのコードはどうなるのかな
|
||
- というか、fbdelayで生成されたコードをMIRに持ってくときにどうなるのか?
|
||
- Value型を単なるCodeじゃなくてDelayとして特別な値にリダクションすればいいのかな
|
||
|
||
[[otopoiesis]]でパラメーターを生成するのにも使える?
|
||
|
||
```rust
|
||
fn synth(freq,gate){
|
||
osc(freq)*gate
|
||
}
|
||
|
||
fn synth_module(freq:()->float,gate:()->float){
|
||
`||{ synth(freq(),gate()) }
|
||
}
|
||
```
|
||
|
||
これのモジュールを評価すると、freq,gateがUIに現れるという感じでできるのかな(そして、ここでUIの範囲制限をするためにも数値型が範囲を持っていた方がいいということになりそう)
|
||
|
||
うーん、サンクを手動で使わず表現できるような何かが欲しいなあ
|
||
|
||
```rust
|
||
fn wrap_module(param:Param, synth:(Param)->float ){
|
||
`{ | | synth(param |> invoke) }
|
||
}
|
||
```
|
||
Param型はinvokeでuiからの値を取れる、サンクをアンラップするようなメソッドを持つ型クラスに属している、という感じで、ジェネリクスが実装出来たらいけそうね
|
||
|
||
|
||
## マクロを提供するプラグイン
|
||
|
||
プラグインも、各関数が返す型と同時にどのレベルでその関数が使えるかをコンパイラに渡す必要がある
|
||
### ファイル読み込みはどうなる?
|
||
|
||
[[mimiumのファイルIO]]の話。マクロの展開より先に型の評価が行われるので、例えばファイルを読み込んでからチャンネル数が分かる、みたいな場合型情報を読み込む段階でファイルを読みこまなければならない、がその評価のためにはマクロを実行して文字列の値を受け取らなければならない、、、ということで、結局[[依存型]]がないと多段階計算でも対応できない。
|
||
|
||
結局、読み込んで返す値として次元数の値を持つ配列を返すしかないのかも
|
||
|
||
ただ、よく考えると[[Max]]のsfplayとかだって、ファイルのチャンネル数とは別に出力のチャンネル数先に指定するしな(入ってるファイルの中身のCh数に応じて処理を分岐させるというユースケースがあんまり思いつかない)
|
||
|
||
## シンタックス
|
||
|
||
色んな流派があるが、バッククォートでクォート(コード型の値を作る)、$でスプライス(コードを埋め込む)ようにする。
|
||
|
||
```rust
|
||
let x = `{1+2} //コード/クォートのシンタックス
|
||
${x} //エスケープ/スプライスのシンタックス
|
||
//or
|
||
$x
|
||
//or
|
||
$(x)
|
||
```
|
||
|
||
どちらも単なる単項演算子として定義しているので、複雑な式の場合は`()`でグループ化するか、`{}`でブロックを生成すればよろしい。
|
||
|
||
### 型シグネチャ
|
||
|
||
新たにCode型が導入されるので、これもバッククォート演算子を導入することにする。
|
||
|
||
```rust
|
||
let x : `(float)->float //これは関数型に対する宣言
|
||
let y : (float)->`float //これは返り値がCode型の場合
|
||
```
|
||
|
||
レコードとタプル型で曖昧性が出そうな気もするけど、多分大丈夫
|
||
|
||
### シンタックスシュガー
|
||
|
||
ファイル内でステージを変えたブロックを作りたい場合、ブロックがどんどんネストされていって面倒。
|
||
|
||
あと、mimiumではブロック`${}` の内側に`fn`での関数宣言ができないのもダルい。
|
||
|
||
ステージ表記については、一旦0をmacro、1をmainと呼ぶことにする。それ以上にステージが増える可能性もあるけど今はこれで十分
|
||
|
||
```rust
|
||
//ステージ1からスタート
|
||
#stage(macro)
|
||
//ここはステージ0
|
||
let x = {...}
|
||
|
||
#stage(main)
|
||
//ここで戻る
|
||
let y = (x+...)
|
||
|
||
```
|
||
|
||
これをこういう形でネストした式へ変形
|
||
```rust
|
||
`{//コンパイラはステージ0からスタートするためにCodeを追加
|
||
//ステージ1からスタート
|
||
${//#stage(macro) start
|
||
//ここはステージ0
|
||
let x = {...}
|
||
`{//#stage(main) start
|
||
//ここで1に戻る
|
||
let y = (x+...)
|
||
}//#stage(main) end
|
||
|
||
}//#stage(macro) end
|
||
}// main code end
|
||
```
|
||
|
||
この、最後に閉じ括弧が溜まっていく形にしない限り、yはxを参照できなくなってしまう
|
||
|
||
最終的に、ステージは[[mimiumのモジュールシステム|モジュール]]単位で変更していくほうが都合がよさそう。
|
||
|
||
とはいえ、簡易的に1ファイル内でステージ0、1の変更もしたいかな
|
||
|
||
persistentな宣言をした時の意味論はどうしよう(コードを複製する?)
|
||
|
||
persistentである時点でcode型が現れないかのチェックをする必要はある
|
||
|
||
単なる置き換えだと、includeしたファイルの定義によってその下のステージが変わってしまう可能性がある?
|
||
|
||
|
||
|
||
|