quartz-research-note/content/mimium新内部表現の構想.md
Tomoya Matsuura(MacBookPro) c0e01bf856
Some checks are pending
Build / build (push) Waiting to run
[obsidian] vault backup: 2024-06-08 22:50:17[
2024-06-08 22:50:17 +09:00

383 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: "2023-09-20T17:12:17+0900"
---
#memo #mimium #programming-language
[[音楽プログラミング言語の形式化#mimium と 多段階計算]]
[[多段階計算]]を取り入れたい
とりあえず[[The w-calculus a synchronous framework for the verified modelling of digital signal processing algorithms|W Calculus]]を自然に拡張してみる。
$W$ Calculusとmimiumの形式は似ているが、主に2つの違いがある。
1. $W$ Calculus はLinear-Time Invariant なシステムを想定しているため、基本演算は項の加算と、項と定数の乗算しか使えない。
2. $W$ Calculusでは関数が数をとって数を返すものしかない。つまり、関数やfeedの項を取ったり返すような高階関数は想定されていない。
問題になるのは後者の方だ。
## 型
n以下の自然数$I_n$ (ディレイのbounded access用)
$$
\begin{align}
\tau ::=&\quad R_a \quad & a \in \mathbb{N}\\
|&\quad I_n \quad &n \in \mathbb{N} \\\
|&\quad \tau → \tau \quad &a,b \in \mathbb{N}\\
% |&\quad \langle \tau \rangle
\end{align}
$$
とりあえず1要素のタプルと普通のRは区別しないことにする
(そしてよく見るとこれは関数→関数のような高階関数を許してないんだな)
そうか高階関数を考えなければクロージャを考慮する必要もないものな
[[ブロックとサンプルの互換性]]をどうするかが問題?
## 値
一旦タプルについては考えないことにしよう
$$
\begin{align}
v \; ::= & \quad R \\
| & \quad \lambda x:\tau.e \quad & [lambda]\\
|& \quad feed \; x.e \quad & [feed] \\
\end{align}
$$
## 項
$$
\begin{align}
e \; ::=& \quad x \quad x \in \mathbb{V} \quad & [value]\\
|& \quad \lambda x.e \quad & [lambda]\\
|& \quad fix \; x.e \quad & [fixpoint]\\
|& \quad e \; e \quad & [app]\\
|& \quad feed \; x.e \quad & [feed] \\
|& \quad delay \; e \; e & [delay]\\
%%|& \quad (e_1,e_2) \quad & [product]\\
%%|& \quad \pi_n e \quad n\in \mathbb{N},\; n>0 \quad & [project]\\
%%|& \quad \langle e \rangle \quad & [code] \\
%%|& \quad \textasciitilde e \quad & [escape]
\end{align}
$$
基本演算Intrinsicは直感に任せる
本来はfixの中でfeedを使ったり、feedの中でfixを使うとエラーだが、結局シンタックスレベルでは排除できないので型でエラーとして弾くことにする…
いや値レベルでの切り分けは不可能なので、こうする
## 実例
```rust
fn cascade(order:int,fb)->(float->float){
if(order>0){
|x|{
cascade(order-1)(x) *(1-fb) + self*fb
}
}else{
|x| x
}
}
```
ちょっとわかりやすさのために`self`を使わずfeedにしてみる
```rust
fn cascade(order:int,fb)->(float->float){
if(order>0){
|x|{
feed(y) { cascade(order-1)(x) *(1-fb) + y*fb }
}
}else{
|x| x
}
}
```
あー、今までは`fn(x)`でself使うものを`feed(self).lambda(x).e,`って感じに自動的に変換してたけど、変換するとしたら`lambda(x).feed(x).e`の方が良かったってことなんだな
これを`cascade(3,0.9)`とかで簡約してみるか
```rust
cascade(3,0.9)
|x|{
let res = cascade(2)(x);
feed(y) { res*0.1 + y*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = cascade(1)(x2);
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = |x3|{
let res3 = cascade(0)(x3);
feed(y3) { res3*0.1 + y3*0.9 }
}(x2);
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = |x3|{
let res3 = |x|{x}(x3);
feed(y3) { res3*0.1 + y3*0.9 }
}(x2);
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = |x3|{
let res3 = x3;
feed(y3) { res3*0.1 + y3*0.9 }
}(x2);
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = |x3|{
feed(y3) { x3*0.1 + y3*0.9 }
}(x2);
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
let res2 = feed(y3) { x2*0.1 + y3*0.9 };
feed(y2) { res2*0.1 + y2*0.9 }
}(x1);
feed(y1) { res1*0.1 + y1*0.9 }
}
|x1|{
let res1 =|x2|{
feed(y2) { feed(y3) { x2*0.1 + y3*0.9 } *0.1 + y2*0.9 }
}(x1);
feed(y1) { res1 *0.1 + y1*0.9 }
}
|x1|{
let res1 = feed(y2) { feed(y3) { x1 *0.1 + y3*0.9 } *0.1 + y2*0.9 };
feed(y1) { res1 *0.1 + y1*0.9 }
}
|x1|{
feed(y1) {
feed(y2) {
feed(y3) { x1*0.1 + y3*0.9} *0.1 + y2*0.9 }*0.1 + y1*0.9 }
}
```
feedの項に対する加算とか乗算の計算は簡約がしづらいなあ
時間0の時のyは全て0として、
```rust
|x1,y1_ref,y2_ref,y3_ref| {
let y1 = *y1_ref;
let y1_next= {
let y2 = *y2_ref;
let y2_next = {
let y3 = *y3_ref;
let y3_next = x1*0.1 + y3*0.9;
*y3_ref = y3_next;
y3_next
}*0.1 + y2*0.9;
*y2_ref = y2_next;
y2_next }*0.1 + y1*0.9;
*y1_ref = y1_next;
y1_next
}
```
やっぱdenotationalの方が定義しやすいかもなあ
ああでもfeedを無事に展開できるということは、feedの項に対して`Cell`を割り当てることそのものには間違いはないのか
ただ、例えばFeedの項の中に関数が残っちゃうような可能性もあるため、Feedのhistoryの中にLambdaの項が保存されるような状況が回避できない。
型付規則の中でfeed x.eのeがプリミティブというか、Boxedにならないものしか取れないようにすればいいのかね。そうするとValueはCopyトレイトを実装できて、feedの中に実際にはラムダが入ってたとしても、簡約後は必ずValueになっていると
## 型(改正版)
というわけで型の定義再訪
$$
\begin{align}
\tau_p ::=&\quad R_a \quad & a \in \mathbb{N}\\
|&\quad I_n \quad &n \in \mathbb{N} \\
\tau :: = &\quad \tau_p\\
|&\quad \tau → \tau \quad &a,b \in \mathbb{N}\\
% |&\quad \langle \tau \rangle
\end{align}
$$
でラムダ抽象とFeedの型付け規則こういう感じになると
$$
\frac{\Gamma, x:\tau^a \vdash e:\tau^b}{\Gamma \vdash \lambda x.e:\tau^a \to \tau^b }
$$
$$
\frac{\Gamma, x : \tau_p^a \vdash e: \tau_p^a }{\Gamma \vdash feed\ x.e:\tau_p^a}
$$
タプルとかレコードもできるけど、関数をタプルの要素にしたりはできない(できないでもないけど、「そういう型をとれるタプル」と「そういうのできないタプル」を分けて考える必要がある)、って感じでユーザーにはややこしいですねえ
## Feedのスコープと簡約の順番について
ところでさっきの`cascade`関数ってさ、わざわざ高階関数にせず一括でやれるんですかね、
```rust
fn cascade_f(order:int,fb,x)->float{
letrec cascade = if(order>0){
|x|{
cascade(N-1)(x) *(1-fb) + self*fb
}
}else{
|x| x
}
cascade(x)
}
let res = cascade_f(3,0.9,input)
```
ともあれコピーキャプチャのクロージャでも問題はなさそうだけども、この状態だと`cascade`のfeedのコンテキストは毎サンプル終了しちゃうって感じなんだよね
## VMのインストラクションとデータ構造
```rust
type Ref = u8;
enum UpIndex{
Local(Reg),
UpValue(u64)
}
struct FuncProto{
instructons: Vec<Instruction>,
constants: Vec<RawVal>,
upvalue_idxs: Vec<UpIndex>,
feed_idx: Vec<u64>
}
```
この関数だけだとfeedidをどうつければいいかなあ
```rust
fn filterbank(N,input,lowestfreq, margin,filter){
if(N>0){
let freq = lowestfreq+N*margin;
return filter(input,freq)
+ filterbank(N-1,input,lowestfreq,margin,filter)
}else{
return 0
}
}
fn lowpass(input,fb){
input* (1-fb) + self * fb
}
let lowpass = |input,fb|{feed(y) {
input* (1-fb) + y * fb
}} }
let lowpass = |input,fb,ref_y|{
let res = input* (1-fb)+ deref(ref_y)*fb
ref_y := res
res
}
res = filterbank(3,input,100,2000,2.0,lowpass)
```
lowpassは最終的にlambda{feed{self}}的な感じになるが、それはfilterbankの中で呼ばれるまではわからない
lowpassのバイトコードはこんな感じか
```
lowpass: //stack 0:input, 1: fb
moveconst 2 0
sub 3 1 2
getfeed 4
mult 5 2 4
add 6 3 5
retfeed 6 1
const:
1_i64
upindexes:
//nothing
```
```
global_ftable:
lowpass
filterbank
filterbank: //stack 0:N,1:input,2:lfreq,3:margin,4:filter
moveconst 5 0
gt 6 0 5
jmpifneg 6 16
mult 6 0 3
add 7 2 0
move 6 6 7
move 7 4 // get filter
move 8 1
move 9 6
callcls 7 2 1 //result on stack 7
moveconst 8 1
sub 9 0 8
move 10 -1 // get recursive call
move 11 9 // prepare arguments...
move 12 1
move 13 2
move 14 3
closure 15 4 // hof requires all function should be closure
call 10 5 1 //recursive call, result on stack 10
add 0 7 10
jmp 1
moveconst 0 0
ret 0 1
const:
0_i64
-1_u64
```
feedが呼ばれた時にどのfeedidかを判別するのはランタイム側の役目
---
```rust
fn cascade_f(order:int,fb,x)->float{
letrec cascade = if(order>0){
|x|{
cascade(N-1)(x) *(1-fb) + self*fb
}
}else{
|x| x
}
cascade(x)
}
fn phasor(freq)->float{
1+self
}
fn doubleosc(freq)->freq{
phasor(freq)+phasor(freq+10)
}
let res = cascade_f(3,0.9,input)+doubleosc(440)
```
なんかこんな感じだとして、レキシカルに何番目の関数呼び出しか、
[[mimiumの中間表現を考える]]