| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Control.Monad.Circular
Description
Knot-tying monad transformer for recursive graph building.
Some operations, such as building a graph, are inherently self-recursive; consider the following graph:
a -> b b -> a
To construct in Haskell, we might want to use the following type:
data Node = Node
{ nodeName :: Text
, nodeNeighbours :: [Node]
}To construct our trivial graph, we need a to know about b and b to know
about a: this is fine as long as we can build them both at the same time:
graph = [nodeA, nodeB]
where
nodeA = Node "a" [nodeB]
nodeB = Node "b" [nodeA]But this falls apart as soon as building the nodes becomes more complicated; for instance, if it becomes monadic. This causes an infinite recursion:
graph = do
a <- buildA
b <- buildB
pure [a,b]
where
buildA = do
b <- buildB
pure $ Node "a" [b]
buildB = do
a <- buildA
pure $ Node "b" [a]The reason why the non-monadic version works is laziness; and there is a way
to retrieve this laziness in a monadic context: it's what MonadFix is for.
(https:/wiki.haskell.orgMonadFix)
However, MonadFix is both powerful and unintuitive; the goal of this module
is to use its power, but to give it a more restricted interface, to make it
easier to use. Using CircularT, the graph above can be built monadically
like so:
graph = runCircularT do
a <- buildA
b <- buildB
pure [a,b]
where
buildA = withCircular "a" do
b <- buildB
pure $ Node "a" [b]
buildB = withCircular "b" do
a <- buildA
pure $ Node "b" [a]It allows each part of a recursive process to be given a name (the type of which is of the user's choosing), and it automatically breaks cycles. The only caveat is that we cannot violate temporal causality: if we attempt to make a cache-building decision based on the value obtained from the cache, then no amount of laziness can save us:
broken = runCircularT go
where
go = withCircular () do
x <- go
pure $ if odd x then 1 else 0CircularT is somewhat similar to TardisT from Control.Monad.Tardis and
SchemaT from Hasura.GraphQL.Parser.Monad, but simpler than both.
Documentation
newtype CircularT k v m a Source #
CircularT is implemented as a state monad containing a lazy HashMap.
We use this state to both determine wether we have already encountered a
given key and to track the associated result. We use laziness and MonadFix to
tie the knot for us (see withCircular).
- type
kis the type of cache key, to which a given action is associated. - type
vis the values we wish to cache in our process. - type
mis the underlying monad on which this transformer operates. - type
ais the result of the computation
Instances
| MonadWriter w m => MonadWriter w (CircularT k v m) Source # | |
| MonadState s m => MonadState s (CircularT k v m) Source # | Allow code in |
| MonadReader r m => MonadReader r (CircularT k v m) Source # | |
| MonadError e m => MonadError e (CircularT k v m) Source # | |
Defined in Control.Monad.Circular Methods throwError :: e -> CircularT k v m a # catchError :: CircularT k v m a -> (e -> CircularT k v m a) -> CircularT k v m a # | |
| MonadTrans (CircularT k v) Source # | |
Defined in Control.Monad.Circular | |
| Monad m => Monad (CircularT k v m) Source # | |
| Functor m => Functor (CircularT k v m) Source # | |
| Monad m => Applicative (CircularT k v m) Source # | |
Defined in Control.Monad.Circular Methods pure :: a -> CircularT k v m a # (<*>) :: CircularT k v m (a -> b) -> CircularT k v m a -> CircularT k v m b # liftA2 :: (a -> b -> c) -> CircularT k v m a -> CircularT k v m b -> CircularT k v m c # (*>) :: CircularT k v m a -> CircularT k v m b -> CircularT k v m b # (<*) :: CircularT k v m a -> CircularT k v m b -> CircularT k v m a # | |
runCircularT :: (Eq k, Hashable k, MonadFix m) => CircularT k v m a -> m a Source #
Runs a computation in CircularT.
withCircular :: (Eq k, Hashable k, MonadFix m) => k -> CircularT k v m v -> CircularT k v m v Source #
Cache a computation under a given key.
For a given key k, and a computation in CircularT that yields a value of
type v, return an action that builds said value v but that prevents
cycles by looking into and populating a stateful cache.