marimo icon indicating copy to clipboard operation
marimo copied to clipboard

show edges for mo.state getters and setters in the dependency graph

Open alefminus opened this issue 1 year ago • 4 comments

Describe the bug

  1. create state
  2. 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)
  3. 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()

alefminus avatar May 22 '24 05:05 alefminus

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?

alefminus avatar May 22 '24 05:05 alefminus

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.

akshayka avatar May 22 '24 22:05 akshayka

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.

alefminus avatar May 23 '24 17:05 alefminus

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

akshayka avatar May 23 '24 19:05 akshayka