cobbpg / elerea

A simple FRP library providing leak-free first-class streams.
BSD 3-Clause "New" or "Revised" License
116 stars 8 forks source link

entangled 'start' calls #12

Open divipp opened 9 years ago

divipp commented 9 years ago

starts below returns 1 instead of 0:

starts = do
    c <- join $ start $ do
        c <- stateful 0 (+1)
        return $ pure c
    join $ start $ do
        c2 <- stateful 0 (+1)
        return $ liftM2 (+) c c2

Intuitively, the first start creates a counter and join do one sampling. Because no other sampling will ever happen, the value of this counter should be 0 in any computation. Then the second start creates another counter and join do one sampling. Moreover, the second start combines the values of the counters which sould be 0.

divipp commented 9 years ago

I checked the corresponding expression in the pure implementation:

starts = let
    c = head $ start $ do
        c <- stateful 0 (+1)
        return $ pure c
    in head $ start $ do
        c2 <- stateful 0 (+1)
        return $ liftM2 (+) c c2

This evaluates to 0.

divipp commented 9 years ago

I went on examining the issue. I have made another test case which may give further insight:

starts' = do
    smp1 <- start $ do
        s <- stateful 'a' succ
        return $ ((,) s) <$> s
    (s, a1) <- smp1
    smp2 <- start $ transfer "" (:) s
    b1 <- smp2
    (s, a2) <- smp1
    (s, a3) <- smp1
    b2 <- smp2
    b3 <- smp2
    (s, a4) <- smp1
    b4 <- smp2
    print ([a1, a2, a3, a4], [b1, b2, b3, b4])

The corresponding pure expression is

starts'' = let
    as = start $ do
        s <- stateful 'a' succ
        return $ ((,) s) <$> s
    s = fst $ head as
    bs = start $ transfer "" (:) s
  in (take 4 $ map snd as, take 4 bs)

Results:

starts'     -->   ("abcd",["b","db","ddb","eddb"])
starts''    -->   ("abcd",["a","ba","cba","dcba"])

My thoughts:

The output of starts' is at least counterintuitive.

During the transformation to the pure semantics, the information about the sampling order is lost. Because of this, the interface of FRP.Elerea.Simple.Pure is insufficient to describe some meaningful sematics of entangled start calls.

Enangled start calls could be prevented by phantom type variables in compile time but that would be an overkill. Runtime errors might be feasible though.

I think it is possible to give meaningful semantics to entangled start calls. What would be the benefit of this? Entangled start calls (with an easier-to-use interface) might be an alternative to clocked elerea networks.

cobbpg commented 9 years ago

Just for reference, here’s the first example with strings:

starts = do
    c <- join $ start $ do
        c <- stateful "a" ('a':)
        return $ pure c
    join $ start $ do
        c2 <- stateful "b" ('b':)
        return $ liftM2 (++) c c2

This returns "aab", i.e. we’re observing the second output of the first signal. This is not totally unexpected, since the documentation states that the sampling action created by start updates the network. Observing the inner state of the network between two samplings is undefined behaviour.

Nevertheless, it would be certainly nice if signals could be used in this manner. To allow this, the superstep should be redefined so the sampling and updating phases are swapped. The first step would have to start with a dummy update that doesn’t really do anything. I think this could be accomplished by defining a distinguished starting state for signals.