These are chat archives for ThoughtWorksInc/Binding.scala

25th
Apr 2016
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:36

Hi there, I'm playing around a bit with Binding.scala (mostly the data-binding stuff not the DOM bits), but I'm a bit stuck on how to achieve a rather simple effect.

What I need is to sort of do a fold over all the values in a Binding as they come in and accumulate some state while doing that. (My case is a bit more complicated, but this'll do for a start :))

What I have so far is to have a Var[T] which is set up with the initial accumulator state and to then have a

```scala

(dammit, sorry, premature post. Will edit and repost :))
杨博 (Yang Bo)
@Atry
Apr 25 2016 10:43
For complex computation, you can store your data into a Binding of immutable collection, then compute your accumulator via methods like foldLeft, sum or any other higher ordered functions:
val source = Var(List(1, 2, 3))
val result = source.each.sum
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:44

(Sorry, this is the full question:)

Hi there, I'm playing around a bit with Binding.scala (mostly the data-binding stuff not the DOM bits), but I'm a bit stuck on how to achieve a rather simple effect.

What I need is to sort of do a fold over all the values in a Binding as they come in and accumulate some state while doing that. (My case is a bit more complicated, but this'll do for a start :smile:)

What I have so far is to have a Var[T] which is set up with the initial accumulator state and to then have a

    var externalValue$ = Var[T](...)
    var currentState$ = Var[S](...)

    val accumulatedState$: Binding[S] = monadic[Binding] {
      val nextState: S = (externalValue$.each, currentState$.get) match {
        // Choose a new state, S, based on the two values in the pattern match
      }

      currentState$ := nextState
      nextState
    }

but seems to be quite cumbersome and I'm sure how/if it can abstracted away into some reusable
building block. (I think I'd have to be able to pass a Monadic block into my abstraction if I want to do
anything other than just a simple (A, S) => S state transition function, perhaps using other variables, etc.
Maybe passing in the Var[T] would be sufficient, but it'd require tupling up the inputs explicitly.)

First of all: Is the above guaranteed to work per the expected semantics of the library? From my little experimentation it seems to, but it's ... inelegant.

Right, but the thing here is that the "collection" I'm working on is itself the "stream of values" that a binding takes over time, if that makes sense. So it'd be something like a streaming fold.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:50
*Dang, sorry type annotations were a bit wrong, fixed
杨博 (Yang Bo)
@Atry
Apr 25 2016 10:50
This message was deleted
How about this?
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:51
Ah, OK, yes, that's a bit more elegant and avoids the scary-looking circular (but not really) reference
杨博 (Yang Bo)
@Atry
Apr 25 2016 10:52
Note the watcher.watch() is necessary, because the monadic block is pure functional and does not change anything until you setup a watch()
Wait
This message was deleted
This is simpler.
My original answer and you code are dangerous because of the := operator in a monadicblock.
We should always avoid := in a monadic block.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:58
Ah, OK, so self-recursion is explicitly permitted?
杨博 (Yang Bo)
@Atry
Apr 25 2016 10:58
monadic block should be pure functional. No assign operator and no side-effect.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 10:59
That definitely makes sense, but I kind of had the impression that ":=" wasn't actually side effecting when used in a "monadic" block. I guess I was mistaken, then :)
Thanks for the help, I'll see how much further I can get.
Aside: I think the documentation could use a little more expansion on the expected semantics of the Binding/Var side of the system. (Assuming I didn't just miss it.)
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:02
Wait, I guess the self-recursion is not acceptable. Let me figure out another solution
I think there is no safe way to implement self-recursion in current version of Binding.scala
It requires additional API
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:08
Interesting. In my exploration (just trying things out to see what works), I've found that as long as you reach some sort of steady state where things are no longer changing everything seems to work out. AFAICT this is because Var doesn't emit any change notifications when you do v := "foo" if the Var backing 'v' already has the value "foo". (Of course this was using tiny examples.)
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:09
I can provide a conversion def createSelfRecursiveBinding(initialState: A, nextState: A => Binding[A]): Binding[A]
A Binding is not an event stream.
It's just a bindable value
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:12
Re: Not an event stream: Right.
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:13
I am still thinking a simpler solution
With the help of createSelfRecursiveBinding, you can have:
val event: Var[E] = ...
val state: Binding[S] = createSelfRecursiveBinding(initialState, state => monadic[Binding] {
  (event.each, state) match {
    // Choose a new state, S, based on the two values in the pattern match
  }
})
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:15
Maybe my thinking is just too stuck in stream-land right now, but the overall architecture of my system is basically built around the idea that all "state" is basically a system of Binding[_] and Var[_] and that any state that isn't strictly a real state (but is essentially just derived from other state, such as responses to Ajax requests generated from Ajax requests generated from user login "button presses", etc.) should be derived without "extraneous" Var[_].
I think createSelfRecursiveBinding would be helpful. (I'm not completely sure it's entirely sufficient for my needs yet, but given that any pure computation can be implemented as a fold, it should get me quite close.)
(and it would definitely be sufficient to implement any other interesting combinators over the "history" of a Binding)
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:18
Note that you'd better not to make E a case class in order to let it always trigger computation for a :=.
If E is a case class, it may accidentially skip computation because of it detects no change.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:19
Ah, right, yes. Thanks for that tip! I had been wondering how to do pure "trigger" type behavior :)
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:20
Or you can make E a case class, and provide an ID for each instance.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:21
Yup, that had been my "fallback" in case I couldn't find any other solution :). (Or embed a timestamp or something.)
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:21
case class E(id: Long, otherFields: ....)
Is there any other solution except createSelfRecursiveBinding?
I am still looking for a more elegance solution.
杨博 (Yang Bo)
@Atry
Apr 25 2016 11:29
I am reluctant to implement a createSelfRecursiveBinding because it couples with the computation order. It's not a simple and pure expression.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 11:36
The only other way I can think of off-hand would be to somehow allow access to the "previous" value of a Binding/Var. I obviously don't know much about the internals, but I guess that might be more workable? (Of course that's mostly just an underhanded way of doing the same thing, so YMMV.)
Of course that would have some cost even for cases where the "previous" value is never actually used for anything, so...
杨博 (Yang Bo)
@Atry
Apr 25 2016 13:44
There is a get method in each Binding, not only in Var. The get method provides the same meaning as previous you suggested. However, I marked Binding.get as private in order to avoid abusing.
@BardurArantsson, I just thought about you problem. I wonder why you need an explicit currentState variable. Could you refactor your code to avoid explicit state?
I have an example about how to create a very complex state machine while still do not define any explicit state varible. See https://thoughtworksinc.github.io/Binding.scala/#3 . I hope the example help you.
Bardur Arantsson
@BardurArantsson
Apr 25 2016 15:49

Re: Binding.get. Is there still an computation order dependency if Binding.get were made public?

The state exists as a reification of an authorization flow which is built on an authentication flow which is using Ajax, but which can also be "reset" by a user (imagine a button click, it's just a synchronous method call). The authentication flow's state solvable already because that's not dependent on any "history" (it's just e.g. Initialized, Busy, Error, Authenticated depending on where we are in the Ajax flow). However, the Authorization state depends on the "history" of what has happened -- the Authentication events, the "reset" trigger and the previous state. For example if the user tries to authenticate, but authentication fails, I really want to preserve the user's current authorization state, i.e. if already logged in, there's no need to discard those credentials if an error occurs. (Imagine that a single human user may want to log in as different "logical" users.)

I'll have a look at the example, but for now I think I may be better served with a more streaming-based library. Thanks for all the help!
(I suppose I should add: The key here is that is an "open" flow, the user can choose to authenticate at any time when the app is open, so I cannot "funnel" the user through either.)
杨博 (Yang Bo)
@Atry
Apr 25 2016 16:05
You don't need an Authorization state variable. Instead, you just let Initialized, Busy, Error be methods.
Like clickIt or didItWork methods on https://thoughtworksinc.github.io/Binding.scala/#3