Can you recall how we said that event streams and dynamics are general
in terms of the backbone monad but we assume it to be IO
. Now time has come
to find what monad can we use here. By default in the interpreter session
it will be specialized to IO
. But it’s useful to know that it can be more generic.
It turns out that to be able to use a monad in place of IO
we need to
have following properties:
-
we should be able to run concurrent processes with it (instance of
MonadBaseControl
) -
we should be able to run IO-procedures (instance of
MonadIO
) -
we should be able to use mutable variables with it (
IORef
orTVar
are defined as presets)
It’s all encapsulated in the type class Frp
:
class class (IsRef (Ref m), MonadBaseControl IO m, MonadIO m) => Frp m where
type Ref :: (* -> *) -> * -> *
type family Ref m
The MonadBaseControl
is a scary beast that let’s us to use concurrency features
on generic monads not only on IO
. It looks complicated but there are standard ways
to easily derive the instances.
The MonadIO
is a class that let us execute IO inside our monad:
clas MonadIO m where
liftIO :: IO a -> m a
The Ref
is an interesting thing. It’s a special type tag that
let us choose between two predefined defaults for storing internal state
update values. We can choose IORef
or TVar
. The IORef
is better to use for
fast interactive applications like OpenGL graphics game engines. TVar
can be
more suitable for slow apps like GUIs or TUIs.
So in many places for simplicity I’ve omitted the Frp m
constraint
but it’s ubiquitous in the library. For example:
apply :: Frp m => Dyn m (a -> b) -> Evt m a -> Evt m b
So the m
is constrained by Frp
class.
As a user of the FRP libraries you may encounter a specific monad
in the bindings. Often it’s called Run
monad and it contains magic global variables
that are needed to run the interactive application.
How to create our own instances of Frp
class we will know soon from the chapter
That is dedicated to creation of bindings to other libraries.