I am specifically questioning the the example found here where the declarative solution is recommended over the imperative one, but I couldn’t find a clear reason why (it says “declarative approach enhances code maintainability and efficiency.” but doesn’t go further).
To me, both approaches in that example achieve the desired outcome in relatively the same manner, where the underlying dataframe turbines
is filtered and assigned as the value to the Tabulator
widget whenever any of the 3 parameters change in value.
Unless the declarative (.rx
) approach evaluates the output lazily with some sort of planner, I don’t see why it would be more efficient?
P.S. doesn’t pn.rx
actually incur additional overhead since it abstracts away the dependency tracking and management (chains operations implicitly)?
My understanding is the main recommendation is to avoid param.watch
(side effects, or functions that don’t return anything, but modify an object’s param/property instead) because as the app grows, its hard to track down its dependencies.
rx
on the flip side, tracks the dependencies, but can be slightly unintuitive starting out rx Marks the Spot: A Prescription for Reactive Python | by Andrew Huang | Medium
The sweet spot for me is using pn.bind
. Developer Experience — Panel v1.6.3
Thanks for the clarification.
So, from an efficiency point of view, I don’t think establishing interactivity using side effects (via param.watch
or pn.depends
) are undesirable, right? I guess it’s just less preferred for maintainability?
Because in a real project for my work, I wrote a fairly complex (1.5k lines of code) yet performant app in Panel that achieves all its interactivity through side effects, as I almost never found a relevant use case for the return
value from a reactive (bound) function (pn.bind
is syntactic sugar around pn.depends
anyways).
I have written a short summary here on the different ways to establishing interactivity in Panel, could you kindly give it a look and let me know if I got most of the concepts right or not?
I’m not 100% familiar with the Panel reactive internals so I’ve forwarded your question on our Discord channel Discord
1 Like
The main argument for a declarative or reactive approach is that it lets the user specify precisely what they want to happen at the user-visible level, and then the underlying watching mechanisms can be invoked automatically to make that happen behind the scenes. If you try to use the watching mechanism directly to build a complex system, you can be more explicit but you end up having to run a state machine in your own head rather than letting Param handle that. Humans are not great at running state machines (this invokes that, which invokes that, which notifies that, which responds to that, which sends a message to that, and eventually something happens), and so it’s increasingly cognitively difficult to reason about such a system as it gets more complex and changes over time, making the likelihood of major errors or unhandled states increase dramatically. Whereas declarative approaches like reactive expressions or bound functions are complete specifications of the desired outcome, each standing alone semantically even if they are linked in shared dependency chains, making them vastly easier to reason about.
I think some people have already rewired their brains into thinking like state machines or event loops, due to working with other GUI toolkits based on message passing or other lower level approaches, and so they like the more detailed control they get by thinking about each individual event. But I try never to let that happen to my own brain!
- Using
pn.bind
(also declarative approach): pn.bind(func, p1, p2, ...)
is basically a wrapper (syntactic sugar) around @pn.depends
and hence is heavier due to overhead and not preferred. Only use pn.bind
if you really need to use the bound function’s return for some bussiness logic (dynamically computed values) in addition to reactive UI updates/rendering.
I don’t think that’s accurate. @pn.depends
is appropriate within a Parameterized class, where it expresses a direct relationships between the Parameters defined in the class and the methods that depend on them. The decorator is perfect for establishing a clear link there, where the Parameter exists independently of some future widget that might represent it in a GUI. Outside of a Parameterized class, there aren’t any Parameters separate from widgets, and in this context pn.bind
being built on pn.depends
is simply an historical artifact, an implementation detail that should not be determining architecture choices. And the pn.bind
approach has an absolutely crucial advantage over pn.depends
outside of a class in that the binding can be deferred until the function is actually used in the context of a Panel app, thereby cleanly separating the underlying functionality of your function from its attachment to a widget in your GUI app. See Widget-binding API · Issue #1629 · holoviz/panel · GitHub for the reasoning involved here.
If pn.bind
being a wrapper around pn.depends
is truly slowing anything down (which seems doubtful), I’d support pulling it out and eliminating the extra function call. But definitely don’t use pn.depends
in preference to pn.bind
, because then you are tying up your non-GUI code with your GUI code in an inextricable mess, which makes it hard to make full use of your non-GUI code when you someday want to use it without Panel.
1 Like
Thanks a lot for the rich explanation, definitely helps a lot.
Quick followup questions:
- When using
pn.depends
outside of a Parameterized class (for GUI binding), and setting watch=False
, wouldn’t that also defer the binding until the decorated-function is used for UI rendering (when laid out) in Panel, similar to what pn.bind
does?
- I understand that with
pn.bind
you intend to keep your function Panel-agnostic (pure business logic), but even if you make it “tied” to your GUI with pn.depends
, you could technically still reuse (call) it anywhere (for value return or side effects) as a pure Python function as long as you match the function’s signature (I don’t quite see an issue with that?)