Skip to content

Commit

Permalink
generalize lift() and @lift()
Browse files Browse the repository at this point in the history
@lift: allow single observable, or no observables at all
lift: allow operating on non-observables
  • Loading branch information
aplavin committed May 30, 2024
1 parent f3e9013 commit 39a193f
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 18 deletions.
21 changes: 5 additions & 16 deletions src/interaction/liftmacro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,15 @@ end
"""
Replaces every subexpression that looks like a observable expression with a substitute symbol stored in `exprdict`.
"""
function replace_observable_expressions!(exp::Expr, exprdict)
function replace_observable_expressions(exp::Expr, exprdict)
if is_interpolated_observable(exp)
error("You can't @lift an expression that only consists of a single observable.")
exprdict[exp]
else
for (i, arg) in enumerate(exp.args)
if is_interpolated_observable(arg)
exp.args[i] = exprdict[arg]
else
replace_observable_expressions!(arg, exprdict)
end
end
Expr(exp.head, replace_observable_expressions.(exp.args, Ref(exprdict))...)
end
return exp
end

replace_observable_expressions!(x, exprdict) = nothing
replace_observable_expressions(x, exprdict) = x

"""
Replaces an expression with `lift(argtuple -> expression, args...)`, where `args`
Expand Down Expand Up @@ -73,15 +66,11 @@ macro lift(exp)

observable_expr_set = find_observable_expressions(exp)

if length(observable_expr_set) == 0
error("Did not find any interpolated observables. Use '\$(observable)' to interpolate it into the macro.")
end

# store expressions with their substitute symbols, gensym them manually to be
# able to escape the expression later
observable_expr_arg_dict = Dict(expr => gensym("arg$i") for (i, expr) in enumerate(observable_expr_set))

replace_observable_expressions!(exp, observable_expr_arg_dict)
exp = replace_observable_expressions(exp, observable_expr_arg_dict)

# keep an array for ordering
observable_expressions_array = collect(keys(observable_expr_arg_dict))
Expand Down
14 changes: 12 additions & 2 deletions src/interaction/observables.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# lift makes it easier to search + replace observable code, while `map` is really hard to differentiate from `map(f, array)`
const lift = map
# mimick Observables.jl map() signature to forward directly:
lift(f, arg::AbstractObservable, args...; kwargs...) = map(f, arg, args...; kwargs...)
# handle the general case:
function lift(f, args...; kwargs...)
if !any(a -> isa(a, AbstractObservable), args)
# there are no observables
f(args...)
else
# there are observables, but not in the first position
lift((_, as...) -> f(as...), Observable(nothing), args...; kwargs...)

Check warning on line 10 in src/interaction/observables.jl

View check run for this annotation

Codecov / codecov/patch

src/interaction/observables.jl#L10

Added line #L10 was not covered by tests
end
end

"""
Observables.off but without throwing an error
Expand Down
4 changes: 4 additions & 0 deletions src/scenes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ function Observables.onany(@nospecialize(f), @nospecialize(scene::Union{Plot,Sce
return to_deregister
end

lift(f, @nospecialize(arg::Union{Plot,Scene}), args...; kwargs...) = map(f, arg, args...; kwargs...)

# map! and map are for backward compatibility
# can move everything to use lift() in the future
@inline function Base.map!(f, @nospecialize(scene::Union{Plot,Scene}), result::AbstractObservable, os...;
update::Bool=true, priority = 0)
# note: the @inline prevents de-specialization due to the splatting
Expand Down
10 changes: 10 additions & 0 deletions test/observables.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
@testset "lift macro" begin
u_noobs = "a"
x = Observable(1.0)
y = Observable(2.0)
z = (x = x, y = y)

noobs = @lift u_noobs * "b"
@test noobs == "ab"

noobs = @lift $u_noobs * "b"
@test noobs == "ab"

xx = @lift $x
@test xx[] == 1.0

t1 = @lift($x + $y)
@test t1[] == 3.0
t2 = @lift($(z.x) - $(z.y))
Expand Down

0 comments on commit 39a193f

Please sign in to comment.