-
-
Notifications
You must be signed in to change notification settings - Fork 41
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
callback! function refactoring #39
Conversation
Very nice - what you've implemented here is very similar to the current Python version, which will really help. There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished). I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it? Unfortunately I guess that would require a manual type check to find the first f(app::dashApp, outputs::Output..., inputs::Input..., states::State...) but apparently not 🤷 The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list. So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like. |
Unfortunately the function can only have one varargs argument
To be honest, I didn't really understand the problem, could you explain it in more detail and give an example? In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements. callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) .... |
With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible: callback!(app,
Output("my-div", "children"),
State("my-input", "value"), #inportant value
make_inputs()... #long generated list of inputs
) do important_state, args...
return args[important_state]
end |
If the only syntax we provide is varargs, then a single-output callback would look like: callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end And a multi-output callback would look like: callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be: callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if |
Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):
Let's enforce the ordering |
I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size: function output_string(deps::CallbackDeps)
if length(deps.output) == 1
return dependency_string(deps.output[1])
end
return ".." *
join(dependency_string.(deps.output), "...") *
".."
end So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too. |
I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user. I.e. 2 overloads: function callback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State} = State[]
)
end
function callback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end |
@alexcjohnson in 7c36096 I added a flat version of |
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.
This looks great! Very nicely done, and excellent tests. 💃
Refactoring of callbacks
callid
removedInput
,Output
andState
addedinputs..., states...
CallbackId
renamed toCallbackDeps
and removed from exports (i.e. from public API)Now the only way to set the callback is like:
The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance