tomoyanonymous / mimium-rs

minimal musical medium- an infrastructural language for sound and music.
Mozilla Public License 2.0
9 stars 1 forks source link

Closures should be "deep" copied when passed as an argument of higher-order function #100

Open tomoyanonymous opened 1 week ago

tomoyanonymous commented 1 week ago

Currently, this type of code that replicates oscillator with recursive function, works.

fn osc(freq,rate){
  ...
}

fn replicate(n,gen:()->(float,float)->float){
    if (n>0.0){
        let c = replicate(n - 1.0,gen)
        let g = gen()
        |x,rate| g(x,rate) + c(x+100.0,rate+0.1)
    }else{
        |x,rate|  0
    }
}
let mycounter = replicate(20,| | osc);

However, this code does not work.

fn osc(freq,rate){
  ...
}
fn replicate(n,gen:(float,float)->float){
    if (n>0.0){
        let c = replicate(n - 1.0,gen)
        |x,rate| gen(x,rate) + c(x+100.0,rate+0.1)
    }else{
        |x,rate|  0
    }
}
let mycounter = replicate(20, osc);

This is because the closure is heap-allocated and the real value on the stack is the pointer to the closure.

When the instantiated closure is passed as an argument, the closure is "shallow" copied and its statestorage are shared between cloned instances. I was wondering which spec was correct for a while, but now I think "deep" copy is the right choice for this case.

I'm not sure if there are really no problems around upvalue treatise, but it works if my guess is right.

To implement this, add something like "CloneClosure" instruction to the MIR and bytecode, is the straightforward way.