show edges for mo.state getters and setters in the dependency graph
Describe the bug
- create state
- call getter and setter from same function (found it while trying to implement a log for serial connection date, example shows an example of a counter)
- invoke function
Expected: function call cell evaluated triggering all getter dependent cells
Actual: Endless loop where it seems the function call cell triggers re-evaluation of itself via the function definition cell being updated?
Environment
{
"marimo": "0.6.0",
"OS": "Linux",
"OS Version": "6.8.7-300.fc40.x86_64",
"Processor": "",
"Python Version": "3.12.3",
"Binaries": {
"Browser": "--",
"Node": "v20.12.2"
},
"Requirements": {
"click": "8.1.7",
"importlib-resources": "missing",
"jedi": "0.19.1",
"markdown": "3.6",
"pymdown-extensions": "10.8.1",
"pygments": "2.18.0",
"tomlkit": "0.12.5",
"uvicorn": "0.29.0",
"starlette": "0.37.2",
"websocket": "missing",
"typing-extensions": "missing",
"black": "24.4.2"
}
}
Code to reproduce
import marimo
__generated_with = "0.6.0"
app = marimo.App()
@app.cell
def __():
import marimo as mo
return mo,
@app.cell
def __(mo):
get, set = mo.state(0)
return get, set
@app.cell
def __(get, set):
def callboth_to_advance():
x = get()
set(x + 1)
return callboth_to_advance,
@app.cell
def __(callboth_to_advance):
callboth_to_advance()
return
@app.cell
def __(get):
get()
return
if __name__ == "__main__":
app.run()
Importantly the case of a single cell containing getter and setter does work (does not cause an infinite loop, only evaluates once when caused to evaluate), so is there some special handling for it? perhaps it can be elaborated to cover this? or maybe a complete check for loops should be done (i.e. walking the graph) and avoided? Special edges in the dependency graph also could help, showing the Cell_containing_setter_call -> Cell_containing_getter_call connection?
call getter and setter from same function (found it while trying to implement a log for serial connection date, example shows an example of a counter)
For such cases, pass a function to set_state instead of calling get_state to avoid re-queuing the cell for execution: set_state(lambda v: v + 1).
so is there some special handling for it?
There is some special handling, described in this section:
https://docs.marimo.io/guides/state.html#updating-state
We don't do much any runtime analysis of the graph (it opens up a can of worms that is difficult to reason about). We might be able to add a debugging feature that highlights "state" edges in the dependency viewer.
My preference is to leave as is -- I know it's confusing but I don't see any clean solutions -- and instead encourage people to pass functions to set_state.
Alright, I thought working on the additional edges in the dependency sidebar would be cool, so if you change your mind about it let me know :) Closing for now.
Alright, I thought working on the additional edges in the dependency sidebar would be cool, so if you change your mind about it let me know :) Closing for now.
Oh, yes that would be cool! I suppose it could even be done with some careful static analysis.
I was mostly expressing hesitation of detecting cycles at runtime, or further modifying the rules of how state updates are propagated. But if you have ideas I'm open to hearing them