-
-
Notifications
You must be signed in to change notification settings - Fork 77
Revamped Reactive Expressions notebook #858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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. |
looking it over now |
Since #859 has been merged and fixed the ipython module being automatically imported when Param is imported, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gone ahead and committed a revision of several parts of this notebook in an effort to enhance clarity for non-param-experts like myself.
I could use a quick check if my mental model is fully accurate, but based on what I could understand, I've tried to expand on the comparison of the reactive expression approach to the existing 'push' and 'pull' models in Param. Further, I've described the .bind
approach as a bridge between the 'push' and reactive 'rx' models... largely with the benefits of reactive programming and the debuggability of more explicit approaches. Does that seem correct?
I've also removed the import of param.ipython (and mention of this magic) in response to Maxime's comment above.
I'd appreciate a quick read-through from someone, but otherwise, I think I'm done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't yet read your changes, a first reaction on introducing comparisons to the push/pull model is that I'm questioning whether this makes it easier for users to understand. I've had feedbacks that the Param user guides are too academic and I think that's one example of that 🙃 There's a bit of a terminology to understand for Param users, callbacks, events, depends, Parameters, and now there's even more with reactive expressions and functions. If we can not mention "push/pull" at all, I'd say it's a win.
(I'd bet the "pull" model (I mistakenly wrote "push" first hehe) - which is mostly numbergen
- is barely used, if at all these days, one could argue numbergen should be a different project)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I share your concern and the fact that I found this guide previously too academic is exactly why I included the more intuitive language, descriptions, and use-case examples instead of just referring to the other APIs as "Dependencies and Watchers" and "Dynamic parameter values". In general, I think the introduction of a new API does require a comparison to existing modes of working with a package, even if only to convey how the new API serves a largely non-overlapping user base with the other approaches. I hope I achieved something reasonable in the revised notebook, but I'm open to further suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My immediate feeling is that we should minimize referring to dynamic parameter values, so I'd strongly prefer if we didn't compare and contrast the new API(s) against that. It's not a model that is used by any of our code these days and I would probably prefer if it stayed that way, so don't want to direct people towards that in any way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok I'll just adjust to remove any mention of the pull approach in this notebook
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read the whole guide and still think it can be improved (happy to work on that!):
- the guide relies too much on being executed in a notebook, I don't think you can understand what's going on without executing it to be honest. Either it needs to be updated or we need to allow users to execute the code (e.g. nbsite-pyodide directive, jupyterlite deployment, link to download the notebook).
- if we want this API to feel very generic, I don't think we should start with a DataFrame example, for instance some simple string manipulation should do
- explain that reactive expressions are by default not-reactive, and that's in line with the default behavior of
param.depends
andparam.bind
, they only become reactive when a framework supports them, and that Param happens to provide a built-in support for notebooks to automatically refresh the output of a reactive expression. - explain that setting a reactive expression with
.rx.value = <>
can only be done on the root - explain what happens when a reactive expressions has multiple "inputs", e.g. does it cache a re-use the value from a branch that is not updated?
"metadata": {}, | ||
"source": [ | ||
"Now, let's make a `Parameterized` class with some `Parameters`, here named `a` and `b`, that we'll want to add together reactively. We will also import `param.ipython` to install support for displaying \"live\" reactive components in an IPython or Jupyter notebook, re-rendering themselves when the inputs change." | ||
"Without `rx()`, adding 1 to `i` would have immediately invoked integer addition in Python, assigning an integer 2 to `j`. However, because we made `i` a reactive expression, what happens is that `i` stores its input value on an internal attribute called `_obj`, while overloading `+` to not just calculate `i + 1`, but also return another reactive object that records the operation (`i + 1`). This stores the dependency so that whenever `i` changes, the reactive object knows that it needs to update itself by re-executing `i + 1`." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's something wrong, right?
while overloading
+
to not just calculatei + 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the question here?
"metadata": {}, | ||
"source": [ | ||
"Ok, now we can use `param.bind` to \"bind\" parameters `a` and `b` to create a reactive function:" | ||
"## Limitations\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another limitation I believe we should add is that if i = rx(1)
, then isinstance(rex, int)
is False
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you were proposinga fix for isinstance , so I didn't mention that. I agree it is focused on beginning able to execute it, and it would be great if it were live in the website because reactivity is hard to appreciate without seeing it react. 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you were proposinga fix for isinstance , so I didn't mention that
I suggested there was a way for insintance
to operate on the underlying value that I think Philipp wasn't aware of; I don't think we've decided yet whether that's the right thing.
Answering to @jbednar in the main thread about:
I meant that the pipeline of a reactive expression isn't executed automatically when the |
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. |
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 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 expressi
62FB
on lazy and when is it non-lazy (e.g. watching the reactive expression with |
While it could be improved, the guide is already in a pretty good shape! Thanks all for contributing. |
Reactive_Expressions.ipynb previously had three sections depending on these concepts:
rx()
and.value
Parameterized
,Parameter
,bind
.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 intoparam.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:rx()
and.rx.value
(complete basics, i.e. "what it is" and "that it works")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.