Skip to content
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

Feature request: Option for on to also get previous value #108

Open
schlichtanders opened this issue May 22, 2023 · 5 comments
Open

Feature request: Option for on to also get previous value #108

schlichtanders opened this issue May 22, 2023 · 5 comments

Comments

@schlichtanders
Copy link

Hi,

first time I thought Observables.jl are just the right thing for my task, however it seems they are not. I want to listen on updates of on object, however then I also want to compute more detailed update information. For this I would need the previous value.

As far I understood, I would need to create an Observable of a Pair (or Tuple) prev => current so that I can access the previous element.
It would be much nicer if the Observerable could just bear the current value and the prev will be passed to the ObserveFunction on an update.

@hhaensel
Copy link

About a year ago, I proposed to add a generalised validity check (#96 ), which goes in a similar direction as your proposal. However, in my proposal there would be only one single callback function that obtains the previous value and decides whether to trigger the listener functions or not.

@yakir12
Copy link

yakir12 commented Sep 12, 2023

I recently needed something similar. The following solved it:

function old(xin)
    xold = Observable(xin[])
    xlast = Ref(xin[])
    on(xin) do x
        xold[] = xlast[]
        xlast[] = x
    end
    return xold
end

Which does this:

julia> x = Observable(1)
Observable(1)


julia> older_x = old(x)
Observable(1)


julia> x[] = 2
2

julia> older_x
Observable(1)


julia> x[] = 3
3

julia> older_x
Observable(2)

@hhaensel
Copy link

Really nice solution!!! 🚀
I had to read it twice to fully understand what's going on.

I'd propose to make it more robust if the eltype of the Obserable is an abstract type. Moreover, the return type could be a Ref rather than an Observable. Finally, I added the max priority in order to make sure the update is always executed first.

function lastvalue(observable::AbstractObservable{T}) where T
    old = Ref{T}(observable[])
    last = Ref{T}(observable[])
    
    on(observable, priority = typemax(Int)) do x
        old[] = last[]
        last[] = x
    end

    return old
end

The nice thing with your approach is that you can always look at the last value even after execution, which should make debugging even a bit easier.

If you just want to write a controller that cancels wrong inputs you can get away with even a smaller footprint when you make sure the updater is always called last:

function lastvalue(observable::AbstractObservable{T}) where T
    last = Ref{T}(observable[])
    
    on(observable, priority = typemin(Int)) do x
        last[] = x
    end

    return last
end

Then you can do

julia> o = Observable{Number}(1)
Observable{Number}(1)

julia> old = lastvalue(o)
Base.RefValue{Number}(1)

julia> on(o, priority = 1) do x
           if x > 100
               # resset old value
               o.val = old[]
               println("Values > 100 not allowd, received: $x")
               return Consume(true)
           end
       end;

julia> on(o) do o
           println("old value: $(old[])")
           println("new value: $(o[])")
       end;

julia> o[] = 2
old value: 1
new value: 2
2

julia> o[] = 200
Values > 100 not allowd, received: 200
200

julia> o[]
2

@yakir12
Copy link

yakir12 commented Oct 23, 2023

If this ends up in Observables.jl then I guess you do want the return type of lastvalue to be an Observable rather then an inert Ref. Why not after all...

@hhaensel
Copy link

I don't think this should end up in Observables, because it has a pitfall; if you update the value silently by setting o.val your solution will fail. However, there are quite some scenarios where you want to change the value without triggering actions, in order to avoid infinite loops.
The solution that I offered (#96) would get around that, but I never received any reaction, so I assume that the authors don't see a need for this kind of feature. I would also like to see a notify function with a filter (#106) like notify(priority_filter, o) in order to address listeners according to their level.
The good thing with Julia is that you can plugin these features yourself and I understand that there's always a tradeoff between quantity of features and cleanness of the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants