transient-haskell / transient

A full stack, reactive architecture for general purpose programming. Algebraic and monadically composable primitives for concurrency, parallelism, event handling, transactions, multithreading, Web, and distributed computing with complete de-inversion of control (No callbacks, no blocking, pure state)
MIT License
630 stars 28 forks source link

Applicative instance does not behave as expected #48

Closed harendra-kumar closed 5 years ago

harendra-kumar commented 7 years ago

When running this code

import Control.Applicative ((<$>), (<*>), (<|>))
import Control.Monad.IO.Class (liftIO)
import Transient.Base (keep, threads, async)

main = keep $ threads 1 $ do
    x <- (,) <$> (event 1 <|> event 2) <*> (event 3 <|> event 4)
    liftIO $ putStrLn $ show x
    where
        event n = async (return n :: IO Int)

Four results are expected:

(1,3)
(1,4)
(2,3)
(2,4)

But it actually produces only the last two.

agocorona commented 7 years ago

I reproduce here what I said in the chat, for documentation:

https://gitter.im/Transient-Transient-Universe-HPlay/Lobby?at=59117b408a05641b116408cd

The applicative as is defined now, the operands have no buffers. That means that if one of the operands receive events faster than the other, the events of the first will be lost and only the values that are at the time the slower operand receive a value are returned

That may be fixed using Channels instead of IORefs in the Applicative instance. But I have to take a look.

There should have been four results in the applicative expression of above, but since events happens fast, the first operand has executed 1 and 2 before the second operand begins to execute, so the only two results have 2 in the first element o the tuple.

But using channels would be problematic . Unbounded channels would produce space leaks if one of the operands do not receive any events. bounded channels either can block or can reproduce the overruns that we already have

agocorona commented 7 years ago

Sorry the explanation is not correct. A buffer is not necessary since the result are generated at the frequency of the fastest operand. It's all because the events of the first operand are fired before the second operand of the applicative begin to execute. All the events that happens before the first result of the second operand are lost since the second operand is empty at this time.

That is less a problem than what I supposed, but it is harder to fix.

agocorona commented 5 years ago

I close this for the moment since the philosphy of transient is to return what happens right now, contrary to the philosophy of standard FRP, where recording everything is essential.

The other behaviour can be solved by using a transactional buffers, like Control.Concurrent.TChan or simply using threads 0 as prefix. This should avoid asynchronous thread, so the events will not be processed in parallel.