Loading...

Monad Transformers

In the stream tutorials we mostly used streams in the IO monad. In general, the type SerialT is a monad transformer, @SerialT m a@ represents a stream of values of type ‘a’ in some underlying monad ‘m’. For example, @SerialT IO Int@ is a stream of ‘Int’ in ‘IO’ monad. Similarly, SerialT Identity Int would be a pure stream equivalent to [a].

Similarly we have monad transformer types for other stream types as well viz. ‘WSerialT’, ‘AsyncT’, ‘WAsyncT’ and ‘ParallelT’.

To lift a value from an underlying monad in a monad transformer stack into a singleton stream use ‘lift’ and to lift from an IO action use ‘liftIO’.

>>> import Control.Monad.IO.Class (liftIO)
>>> Stream.drain $ liftIO $ putStrLn "Hello world!"
Hello world!

>>> import Control.Monad.Trans.Class (MonadTrans(lift))
>>> Stream.drain $ lift $ putStrLn "Hello world!"
Hello world!

Using Monad Transformers

Common monad transformers can be used with streamly serial streams, without any issues. ReaderT can be used with concurrent streams as well without any issues.

The semantics of monads other than ReaderT with concurrent streams are not yet finalized and will change in future, therefore, as of now they are not recommended to be used with concurrent streams.

Ordering of Monad Transformers

In most cases it is a good idea to keep streamly as the top level monad. This example demonstrates how various control flow modifying monads can be combined with streamly stream monads.

State Sharing

Serial Applications

Read only global state can always be shared using the Reader monad. Read-write global state can be shared either using an IORef in the Reader monad or using the State monad.

See AcidRain.hs example for a usage of StateT in the serially executing portion of the program.

Concurrent Applications

The current recommended method for sharing modifiable global state across concurrent tasks is to put the shared state inside an IORef in a Reader monad or just share the IORef by passing it to the required functions. The IORef can be updated atomically using atomicModifyIORef.

The CirclingSquare.hs example shares an IORef across parallel tasks.

See also