Skip to content

Commit bfb105e

Browse files
author
Vladimir Ciobanu
authored
Add CofreeT (#26)
1 parent 08e691f commit bfb105e

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Notable changes to this project are documented in this file. The format is based
66

77
Breaking changes (😱!!!):
88

9+
- added the `CofreeT`, the cofree monad transformer in a new module (`Control.Monad.Cofree.Trans`) ([#26](https://github.com/purescript-contrib/purescript-freet/pull/26))
10+
911
New features:
1012

1113
Bugfixes:

spago.dhall

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
, "effect"
77
, "either"
88
, "exists"
9+
, "free"
910
, "prelude"
1011
, "psci-support"
1112
, "tailrec"
13+
, "tuples"
1214
, "transformers"
1315
]
1416
, packages = ./packages.dhall

src/Control/Comonad/Cofree/Trans.purs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
-- | This module defines a lazy implementation of the _cofree monad transformer_.
2+
3+
-- | Given a `CofreeT` f m a:
4+
-- | - 'f' is a `Functor`, generally representing an AST,
5+
-- | - 'm' is a 'Monad', generally representing an effect,
6+
-- | - and 'a' is the type of the annotation.
7+
-- |
8+
-- | Usually, you would use `CofreeT` to annotate an existing AST with
9+
-- | metadata such as source locations, file names, etc.
10+
11+
module Control.Comonad.Cofree.Trans where
12+
13+
import Prelude
14+
15+
import Control.Alternative (class Alt, class Plus, empty, (<|>))
16+
import Control.Apply (lift2)
17+
import Control.Comonad (class Comonad, class Extend, extend, extract)
18+
import Control.Comonad.Cofree.Class (class ComonadCofree)
19+
import Control.Comonad.Env.Class (class ComonadAsk, ask)
20+
import Control.Comonad.Trans.Class (class ComonadTrans)
21+
import Control.Monad.Trans.Class (class MonadTrans)
22+
import Data.Bifunctor (bimap)
23+
import Data.Foldable (class Foldable, foldMap, foldlDefault, foldrDefault)
24+
import Data.Traversable (class Traversable, sequenceDefault, traverse)
25+
import Data.Tuple (Tuple(..))
26+
import Data.Tuple as T
27+
import Effect.Aff.Class (class MonadAff, liftAff)
28+
import Effect.Class (class MonadEffect, liftEffect)
29+
30+
-- | The cofree comonad transformer for the functor 'f'.
31+
newtype CofreeT f w a = CofreeT (Unit -> w (Tuple a (f (CofreeT f w a))))
32+
33+
-- | Construct a `CofreeT` from a lazy computation with an annotation 'a'.
34+
cofreeT
35+
:: forall f w a
36+
. (Unit -> w (Tuple a (f (CofreeT f w a))))
37+
-> CofreeT f w a
38+
cofreeT = CofreeT
39+
40+
-- | Construct a `CofreeT` from a computation with an annotation 'a'.
41+
cofreeT'
42+
:: forall f w a
43+
. w (Tuple a (f (CofreeT f w a)))
44+
-> CofreeT f w a
45+
cofreeT' t = CofreeT $ (\_ -> t)
46+
47+
-- | Unpack `CofreeT` into the inner computation.
48+
runCofreeT :: forall f w a. CofreeT f w a -> w (Tuple a (f (CofreeT f w a)))
49+
runCofreeT (CofreeT f) = f unit
50+
51+
-- | Obtain the annotation stored within a `CofreeT`.
52+
head :: forall f w a. Functor w => CofreeT f w a -> w a
53+
head = map T.fst <<< runCofreeT
54+
55+
-- | Obtain the inner computation stored within a `CofreeT`.
56+
tail :: forall f w a. Functor w => CofreeT f w a -> w (f (CofreeT f w a))
57+
tail = map T.snd <<< runCofreeT
58+
59+
-- Note: This cannot be automatically derived because 'a' also appears in the
60+
-- 'fst' position of the inner Tuple.
61+
instance functorCofreeT :: (Functor w, Functor f) => Functor (CofreeT f w) where
62+
map f (CofreeT inner) = CofreeT $ map (map (bimap f (map (map f)))) inner
63+
64+
instance applyCofreeT :: (Apply w, Apply f) => Apply (CofreeT f w) where
65+
apply (CofreeT innerF) (CofreeT inner) =
66+
CofreeT
67+
$ \_ ->
68+
go <$> innerF unit <*> inner unit
69+
where
70+
go (Tuple f nextF) (Tuple x nextX) =
71+
Tuple (f x) (lift2 (<*>) nextF nextX)
72+
73+
instance applicativeCofreeT :: (Applicative w, Apply f, Plus f) => Applicative (CofreeT f w) where
74+
pure a = CofreeT $ \_ -> pure (Tuple a empty)
75+
76+
instance bindCofreeT :: (Monad w, Alt f, Apply f) => Bind (CofreeT f w) where
77+
bind (CofreeT inner) f =
78+
CofreeT
79+
$ \_ -> do
80+
(Tuple a m) <- inner unit
81+
let (CofreeT next) = f a
82+
(Tuple b n) <- next unit
83+
pure $ Tuple b (n <|> map (_ >>= f) m)
84+
85+
instance monadCofreeT :: (Monad w, Plus f, Apply f) => Monad (CofreeT f w)
86+
87+
instance monadTransCofreeT :: Plus f => MonadTrans (CofreeT f) where
88+
lift = cofreeT' <<< map go
89+
where
90+
go x = Tuple x empty
91+
92+
instance monadEffectCofreeT :: (MonadEffect w, Plus f, Apply f) => MonadEffect (CofreeT f w) where
93+
liftEffect eff = cofreeT' $ go <$> liftEffect eff
94+
where
95+
go a = Tuple a empty
96+
97+
instance monadAffCofreeT :: (MonadAff w, Plus f, Apply f) => MonadAff (CofreeT f w) where
98+
liftAff aff = cofreeT' $ go <$> liftAff aff
99+
where
100+
go a = Tuple a empty
101+
102+
instance comonadCofreeCofreeT :: (Comonad w, Functor f) => ComonadCofree f (CofreeT f w) where
103+
unwrapCofree = extract <<< tail
104+
105+
instance comonadTransCofreeT :: ComonadTrans (CofreeT f) where
106+
lower = head
107+
108+
instance comonadAskCofreeT :: (Functor f, ComonadAsk e w) => ComonadAsk e (CofreeT f w) where
109+
ask = ask <<< tail
110+
111+
instance foldableCofreeT :: (Foldable w, Foldable f) => Foldable (CofreeT f w) where
112+
foldMap f (CofreeT inner) = foldMap go $ inner unit
113+
where
114+
go (Tuple a next) = f a <> foldMap (foldMap f) next
115+
116+
foldr abb b = foldrDefault abb b
117+
118+
foldl bab b = foldlDefault bab b
119+
120+
instance traversableCofreeT :: (Traversable w, Traversable f) => Traversable (CofreeT f w) where
121+
traverse f (CofreeT inner) =
122+
cofreeT' <$> traverse go (inner unit)
123+
where
124+
go (Tuple a next) = Tuple <$> f a <*> traverse (traverse f) next
125+
126+
sequence = sequenceDefault
127+
128+
instance extendCofreeT :: (Comonad w, Functor f) => Extend (CofreeT f w) where
129+
extend f (CofreeT inner) = CofreeT $ \_ -> extend go (inner unit)
130+
where
131+
go w = Tuple (f $ cofreeT' w) $ extend f <$> T.snd (extract w)
132+
133+
instance comonadCofreeT :: (Comonad w, Functor f) => Comonad (CofreeT f w) where
134+
extract = extract <<< head
135+
136+
-- | 'hoist' the effect type using a natural transform.
137+
hoistCofreeT :: forall f w u a. Functor f => Functor u => (w ~> u) -> CofreeT f w a -> CofreeT f u a
138+
hoistCofreeT nm = bimapCofreeT identity nm
139+
140+
-- | 'interpret' the inner functor using a natural transform.
141+
interpretCofreeT :: forall f g w a. Functor g => Functor w => (f ~> g) -> CofreeT f w a -> CofreeT g w a
142+
interpretCofreeT nf = bimapCofreeT nf identity
143+
144+
-- | Both 'interpret' and 'hoist' the inner functor as well as the effect using natural transforms.
145+
bimapCofreeT :: forall f g w u a. Functor u => Functor g => (f ~> g) -> (w ~> u) -> CofreeT f w a -> CofreeT g u a
146+
bimapCofreeT nf nm (CofreeT inner) = CofreeT $ (map (map (map go))) $ map nm inner
147+
where
148+
go :: f (CofreeT f w a) -> g (CofreeT g u a)
149+
go = map (bimapCofreeT nf nm) <<< nf

test/CofreeTExample.purs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module Test.CofreeTExample where
2+
3+
import Prelude
4+
5+
import Control.Comonad.Cofree.Trans (CofreeT, cofreeT, head, tail)
6+
import Data.List (List(..), fromFoldable, (:))
7+
import Data.Maybe (Maybe(..), fromMaybe)
8+
import Data.Tuple (Tuple(..))
9+
import Effect (Effect)
10+
import Effect.Console (logShow)
11+
12+
type IndexedList = CofreeT List Maybe Int
13+
14+
list :: IndexedList
15+
list = cofreeT (go 5)
16+
where
17+
go :: Int -> Unit -> Maybe (Tuple Int (List IndexedList))
18+
go 0 _ = Just $ Tuple 0 Nil
19+
go 5 _ = Just $ Tuple 5 (fromFoldable $ cofreeT <$> (go <$> [4, 3]))
20+
go i _ = Just $ Tuple i (pure $ cofreeT (go (i - 1)))
21+
22+
annotations :: IndexedList -> List (Maybe Int)
23+
annotations il = head il : join (annotations <$> fromMaybe mempty (tail il))
24+
25+
main :: Effect Unit
26+
main = logShow $ annotations list

0 commit comments

Comments
 (0)