holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
412 stars 69 forks source link

Revamped Reactive Expressions notebook #858

Closed jbednar closed 9 months ago

jbednar commented 9 months ago

Reactive_Expressions.ipynb previously had three sections depending on these concepts:

  1. Getting started: rx() and .value
  2. Reactive Functions: Parameterized, Parameter, bind
  3. Reactive Expressions: .rx, Parameterized.param.X.rx(), .value, rx(), .rx.*

The first section depends only on two constructs: rx() and .rx.value, which seems nice and clean as an introduction. After that, though, immediately it dived into Parameters and into param.bind, both of which I consider optional for understanding and using reactive expressions. Instead, I've reorganized it into this outline to introduce concepts only as they are needed, to minimize how much people need to learn before they can start writing code:

  1. Getting started: rx() and .rx.value (complete basics, i.e. "what it is" and "that it works")
  2. Illustrating "how it works", so that it doesn't just seem magic
  3. Illustrating "that it doesn't always work", i.e., the finite list of limitations
  4. How to address the limitations in practice (the .rx namespace)
  5. Using Parameters with reactive expressions
  6. Bind and how/why to use it

Ideally there would also be a section 7 with a technical deep dive, but that's not something I propose to write now, and too much for this single nb.

Hopefully this is an improvement!

We don't necessarily need to address anything about widgets (ipy or Panel) in this release, but we should also keep in mind where those would be introduced, and maybe call them out more than I have so far.

We also may want to split the bind stuff into its own page alongside this one; it makes sense on its own and is a simpler story than reactive expressions in some ways, so it would probably be good to make it consumable without having to read about reactive expressions. But I've kept in here for now just because I don't have time to move it out, which I think someone should do but maybe isn't urgent.

maximlt commented 9 months ago

I fixed linting and added a note in the reactive guide about the API being experimental, since Jim added that note too in the release notes. And it makes sense to me marking it experimental for a little while.

droumis commented 9 months ago

looking it over now

maximlt commented 9 months ago

Since https://github.com/holoviz/param/pull/859 has been merged and fixed the ipython module being automatically imported when Param is imported, import param.ipython must be removed and the explanation about param.ipython must be adapted a little.

maximlt commented 9 months ago

Answering to @jbednar in the main thread about:

I am not sure what you mean about the notebook being required, though; it's required only for the display to update dynamically, not the value.

I meant that the pipeline of a reactive expression isn't executed automatically when the value of the root is updated. It is only executed if you require the value on the resulting expression, or if a framework has registered a reactive display hook, like Param does automatically on import when in an IPython context.

image

jbednar commented 9 months ago

The way I would say that is that whenever the value is accessed, it is always up to date, which seems like all you can ask of a reactive framework. It's lazy, yes, but reactive. So in e.g. a REST API, computed values will always be up to date when requested, without needing any special support in the event loop. All this should be explained, but I don't think it's misleading to show how it behaves in ipython since that's how it would behave for anything querying its value.

maximlt commented 9 months ago

The way I would say that is that whenever the value is accessed, it is always up to date, which seems like all you can ask of a reactive framework. It's lazy, yes, but reactive.

I don't how other reactive frameworks operate, maybe that's how they all work, but trying to be in the position of a newcomer it seems to me that the combination of "lazy" + "reactive" is a bit strange. Why is it lazy by default, is it a design decision or is it the only way it can be?

We should indeed explain the way it works. Taking your REST example, if I build a reactive expression that returns the number of users in a group (group being the input), the result is going to depend on when the query is made. I.e. If I set group.rx.value and the result is computed only later when I access nusers.rx.value or immediately, the number returned can be different.

Generally as a developer I would like to know when the code is executed and how as that's useful for debugging purposes. Questions like the following ones will be asked sooner than later. When is a reactive expression lazy and when is it non-lazy (e.g. watching the reactive expression with .rx.watch(cb) makes it non-lazy)? Are all intermediate values cached? When a reactive expression has multiple inputs and only one of them is updated, is the whole graph re-executed or just the part that is affected by the updated input?

maximlt commented 9 months ago

While it could be improved, the guide is already in a pretty good shape! Thanks all for contributing.