fonsp / Pluto.jl

🎈 Simple reactive notebooks for Julia
https://plutojl.org/
MIT License
4.94k stars 285 forks source link

Mutations do not rerun cells #564

Open DhruvaSambrani opened 3 years ago

DhruvaSambrani commented 3 years ago

Title self explanatory

I can see why mutations don't re-run cells presently. However, is it possible to signal mutations in Julia? Makie.jl uses Observable or something, but I think(but not sure, more of a gut feel) that that has performance issues.

Further should mutations re-run cells? Does it go against the Pluto model somehow?

dralletje commented 3 years ago

As far as I know, there is no easy way to track or get notified by mutations, so it can't be on by default. Would also cause a problem as push!(x, 1) would invalidate itself and then get stuck in a loop.

Im now leaning towards something similar to observablehq mutable, as a macro that prevents the variable inside from being a dependency of the cell, and automatically invalidates the variable after the statement is executed.

Mutations don't fit the pluto-cell-reactive as nicely as immutability is, but it is something I'd rather implement as good as we can than excluding everyone who doesn't have the luxury of making copies of their matrices :P

You got any ideas? Something I'm overlooking?

lungben commented 3 years ago

See #316 Unfortunately, we have not found a good solution there.

fonsp commented 3 years ago

I really don't know what I'm talking about but maybe we can use Cassette to inject tracking code into setfield and similar.

fonsp commented 3 years ago

Also I'll say that I have doubts about this feature, because it encourages 'ambiguous' notebooks, and requires complicated reactivity rules to address them. For example:

x = [1]

last(x)

push!(x, 2)

push!(x, 3)

What is the result of the second cell?

This is already a problem today (because this notebook is legal), but I feel like adding this feature will not solve it. The notebook is still ambiguous, you can still solve it by following Pluto's reactivity rules in your head, and it still depends on cell order. This is yucky yuck (in both cases) because moving cells should be a no-op.

The alternative solution, which is already possible, disambiguates by making the order explicit!

Current solution

is to name your modifications:

x = [1]

last(x3)

x2 = push!(x, 2)

x3 = push!(x2, 3)

This might seem silly because x === x2 === x3, but it makes the reactive order explicit. Not just for Pluto, but for you or anyone else reading the notebook 😊

(Of course, you can also use static arrays: x = [1], x2 = [x..., 2], etc, but that is not the topic here)

Another example

Before:

write(“file.txt”, “hello”)

read(“file.txt”, String)

After:

begin
    file_written = true
    write(“file.txt”, “hello”)
end

begin
    file_written
    read(“file.txt”, String)
end

For a third example, see the docs for PlutoUI.Button

dralletje commented 3 years ago

Tried experimenting with Cassette, but pretty hard (and expensive) to find any functions being called with a !, but that ofcourse doesn't capture all cases. Also you'd have to track what objects get mutated, because mutation to function local variables wouldn't be a problem etc, etc

lungben commented 3 years ago

One idea would be to check after each cell execution for each global variable if its content changed, e.g. by comparing checksums. This could work for small objects, but would probably take too long for large objects.

Is there any way to leverage the compiler to identify which object is mutated?

fonsp commented 3 years ago

We generally need to know the execution order, hence dependencies, before executing cells.

chelate commented 7 months ago

Is it possible to provide macro that allows directly annotating dependency? Like

x = [1]

@mutation (x,1) # first mutation 
    push!(x,2)

@mutation (x,2) #second mutation
    push!(x,3)

where the only effect of the macro is to set the Pluto evaluation order? This avoids using up the very valuable symbols x1 x2 etc.