steveklabnik / frappuccino

Functional Reactive Programming in Ruby.
https://github.com/steveklabnik/frappuccino
MIT License
354 stars 33 forks source link

Return a Behaviour from Stream#count #11

Closed seadowg closed 11 years ago

seadowg commented 11 years ago

This switches up returning an Integer from #count to it returning a Behaviour (FRP's time varying continuous value). We don't need the full Behaviour implementation for #count as we can just use the stepper function from the original FRP implementations (see here).

Stepper

Stepper takes an initial value and a Stream of new values. Basically, evaluating a Stepper at some point in time will return either the initial value (if the Stream is empty) or the last value that occurred on the stream (with respect to the current time). So:

stepper = Stepper.new(0, button.map { 1 })
stepper.now # => 0

button.push
stepper.now # => 1

I'm not sure how you feel about the naming here of both Stepper and the #now operation (that returns the value at the current time). I've heard this concept referred to as other things such as 'Signal' or 'Property' so that might be something to change.

Scan

So we can return a Stepper that represents the count of a Stream I've added the #scan operation to Stream. This takes a zero value and a block and applies the block to the Stream in a similar way to reduce/inject but instead of returning a value it returns another Stream. This means it can operate on an infinite set.

To get a Stepper representing the count of a Stream we can just do this:

Stepper.new(0, stream.scan { |last, current| last + 1 })

One caveat with this implementation of #scan is that unlike other implementations (in Haskell or Scala for instance) it doesn't output the zero value as the first element of the result stream (it instead starts with the actual first result). This is mainly because I wasn't convinced by any way of 'pushing' this value. What would the 'time' of the occurrence be for the zero value for instance?

P.S. Where FRP gets really cool for me is that you wouldn't actually want to have people calling #now in their code: it would be hidden away in whatever they were interacting with. For instance, if we wanted to have some label in a GUI that would show the count of some event stream (and always be up to date) we could do:

Label.new(stream.count)

The idea would be that the GUI framework should take care of staying up to date and that the FRP framework just provides the ability to evaluate the Behaviour (and possibly some hooks for push based Behaviours like Stepper).

</:neckbeard:>

steveklabnik commented 11 years ago

I've heard this concept referred to as other things such as 'Signal' or 'Property' so that might be something to change.

Yeah, "property" might be a bit more clear. I don't think stepper and now are bad, but we should try to stick to similar naming from other things, I guess...

steveklabnik commented 11 years ago

What would the 'time' of the occurrence be for the zero value for instance?

I think you could make an argument that the time of initialization of the stream would be 0day, but I can also see against it, too. This doesn't bother me.

steveklabnik commented 11 years ago

The idea would be that the GUI framework should take care of staying up to date and that the FRP framework just provides the ability to evaluate the Behaviour (and possibly some hooks for push based Behaviours like Stepper).

Right. I might end up building a variant of Shoes (or a library on top of it) that does this kind of thing.

steveklabnik commented 11 years ago

Yeah, looks great. Let's re-name to 'property' rather than 'stepper' and we're good. :) :+1:

seadowg commented 11 years ago

Ok rename done and everything appears to be green on my end.

aanand commented 11 years ago

Just pitching in, as the "when do you emit the first value" question was one I had to answer when writing Wick. I ended up with a "run-loop" style model, where the first value passed into scan is emitted on the next tick. This also enables things like Wick.from_array, which creates a one-off stream which emits all the array's elements on next tick.

I haven't looked closely at Frappuccino, but just thought I'd share my learnings <3

seadowg commented 11 years ago

Ah yeah that would make a lot of sense in a tick based system. Frappuccino is purely push based at the moment so that won't work here but thanks! :smile:

I thought about making them fire for every callback register but this seemed weird and also thought about just doing it immediately but this would mean that no callbacks or properties would ever see it.

In another FRP framework I made it impossible for Events and Behaviours to be created after the start of 'time' so occurrences like this would just be emitted at time 0 when execution actually started.

P.S. For some reason I can't get this comment to have line breaks. Maybe a bug with emailing them in.

steveklabnik commented 11 years ago

<3

seadowg commented 11 years ago

:metal: