takano-akio / ordrea

Push-pull implementation of discrete FRP with totally-ordered switchers
Creative Commons Zero v1.0 Universal
11 stars 2 forks source link

A question on evaluation semantics #6

Open fhaust opened 9 years ago

fhaust commented 9 years ago

I am in the process of converting a project from reactive-banana to ordrea and am pleased to say that (for my usecase) ordrea is magnitudes faster.

I have just finished reading the push-pull paper by Conal Elliot which I gathered is implemented in ordrea. Still I am unsure about the evaluation semantics used in ordrea. Please correct me if I misunderstood something:


Events are introduced into the network through the triggerExternalEvent functions. These run through the network from the front (push) until they are filterEd out and/or hit a Discrete. Along the way they trigger evaluation of unevaluated Discretes and Behaviors (pull). An example for Events would be mouse clicks or keypresses.

Discretes are stateful values that only change in response to incoming Events. stepperD is an endpoint for event streams which will be evaluated when pulled from the "back" of the network. (Is this true? Or are Discretes evaluated "to the end" as soon as the first one is triggered?) An example for Discretes would be the color of something that is toggled by mouse clicks.

Behaviors are stateful values that may change at any time. Behaviors are only evaluated by need. This will happen when an Event is "coming through" or if needed in the evaluation of the network. Behaviors can only be created through externalB that will sample the IO a action given to it. An example for Behaviors would be the current time.


Did I get all of this right?

fhaust commented 9 years ago

Another question that just came up: Does lazyness apply here? That is: If I start a SignalGen (Behavior (Foo,Bar)) and only use the Foo, is Bar and its dependencies ever evaluated?

takano-akio commented 9 years ago

Your understanding of Events, Discretes and Behaviors sounds correct, except when you say stepperD is an endpoint. You can think of a Discrete a as (Event (), Behavior a), where the event fires every time the content of the behavior is possibly changed. When a discrete is updated, all its users are notified, just like when an event fires (i.e. it's pushed).

Regarding laziness, the answer depends on what you mean by evaluated. The library tries to be pure, which means whether an IO action is executed does not depend on whether its result is demanded (unfortunately there is an exception to this rule, namely when you use liftIO in SignalGen). On the other hand, pure function applications are generally left lazy.

This means, for example, the following program prints "foo", "bar", and "f", but not "g".

import Control.Applicative
import FRP.Ordrea

import Debug.Trace

f :: Int -> Int
f x = trace "f" x

g :: Int -> Int
g x = trace "g" x

main = do
  sample <- start $ do
    foo <- externalB $ do
      putStrLn "foo"
      return $ f 1
    bar <- externalB $ do
      putStrLn "bar"
      return $ g 1
    return $ (,) <$> foo <*> bar
  print . fst =<< sample
  print . fst =<< sample
  print . fst =<< sample

I should really write down all these in Haddock, I'm sorry about the lack of documentation.

fhaust commented 9 years ago

I should really write down all these in Haddock, I'm sorry about the lack of documentation.

No sweat ... Actually I am quite happy to have found a FRP implementation that performs well. And if you are familiar with the general FRP concepts the generated haddock documentation is mostly sufficient.

That said there are some functions that are still unclear to me. Scanning the 0.3.0.0 haddocks I find:

Stuff that is clear to me but IMHO would benefit from a little documentation:

But don't worry ... ordrea works to well for me to complain :)