Clear observables created by @lift

What is the proper way of clearing the (perhaps wrong) @lift observable listeners?

Following example has an error

using WGLMakie
time = Node(1.5)
ξ = @lift( 1.0 < $time  < 2.0 ? $time - 1.0 : 0.0*err) #error, err not defined!
xpos = 1:10
ypos = @lift(  $ξ .*(1:1:10) .+ (1-$ξ).*(10:-1:1) )
fig, ax = scatter(xpos, ypos) 
f # everything is OK
time[] = 0.5  #hitting the err conditions
f

Now, I’ve found the error and would like to redefine

ξ = @lift( 1.0 < $time  < 2.0 ? $time - 1.0 : 0.0) # correct!

It is correct but the old listener function (with the 0.0*err expression) is still hanging around and causing troubles…

Any idea how to clear the old listeners (lifted variables) without restarting the julia session?

Hope this clears it up:

julia> x = Node(1)
Observable{Int64} with 0 listeners. Value:
1

julia> obsfunc = on(x) do x
           @show x
       end
(::Observables.ObserverFunction) (generic function with 0 methods)

julia> x[] = 3
x = 3
3

julia> off(obsfunc)
true

julia> x[] = 4
4

julia> y = lift(x) do x
           println("y is still there")
           3 * x
       end
y is still there
y is still there
Observable{Int64} with 0 listeners. Value:
12

julia> x[] = 5
y is still there
5

julia> y = lift(x) do x
           println("new y")
           4 * x
       end
new y
new y
Observable{Int64} with 0 listeners. Value:
20

julia> x[] = 6
y is still there
new y
6

julia> deleteat!(x.listeners, 1)
1-element Vector{Any}:
 (::Observables.OnUpdate{Observables.MapUpdater{var"#307#308", Int64}, Tuple{Observable{Int64}}}) (generic function with 1 method)

julia> x[] = 7
new y
7

julia> empty!(x.listeners)
Any[]

julia> x[] = 8
8

So, if I correctly understand, I have to manually remove the concrete function from the list of listeners of all input observables/nodes that I used during the lift call?

I’ll rephrase it once more. Do I correctly understand that if I want to remove a lifted observable, there is no function that would traverse the “parents” of my observable and deregister?

Is it feasible to write such a function? Or, would it require a bi-directional reference list (of “lifts”) as part of Observable structure, so we know on which objects to deregister?

Yeah this problem with missing links is one of my gripes with Observables. Before I added the observerfunc thing, you couldn’t even remove those with a single off call, you always had to keep the exact function and observable together to unlink them again.

The connections are made via closures though, so in principle it could be possible to scan those for observables and disconnect. Might be hard to implement correctly.

1 Like

It appears that for the specific case of lift, a specific listener object is created where you can access the targeted observable. But there could always be side effects in the listener functions etc.

julia> x = Node(1)
Observable{Int64} with 0 listeners. Value:
1

julia> y = @lift($x * 3)
Observable{Int64} with 0 listeners. Value:
3

julia> x.listeners[1].f.observable
Observable{Int64} with 0 listeners. Value:
3

julia> x.listeners[1].f.observable === y
true

But, you still cannot start from y and find the appropriate x.
:cry:

Thank you Julius for clarification

That’s similar to this problem though:

x = something

function a_function()
    do_something_with(x)
end

If you only have x, I have no idea how to find out it’s referenced in a_function in Julia. Probably need internals that traverse memory structures, something like this would be needed for garbage collection anyway.