haskellrecursion-schemes

Left scan over Cofree-annotated ASTs


I need to extend the definition of left scan to Cofree structures, to accumulate annotations from the root of an annotated AST to the leaves, however I don't understand why my naive implementation scanCofree.. doesn't do anything.

Edit: my first definition of the program type Stmt is wrong, as it doesn't allow recursion where it matters. See answer below.

The definition for rose trees is as follows, and it does the right thing:

data Tree a = Node a [Tree a] deriving (Show)

scan :: (b -> a -> b) -> b -> Tree a -> Tree b
scan f a (Node x ns) = Node a' $ map (scan f a') ns
  where
    a' = f a x

t0 :: Tree Int
t0 = Node 3 [Node 5 [], Node 1 []]
> scan (+) 0 t0
Node 3 [Node 8 [],Node 4 []]

I wrote scanCofree naively following the one above:

data Cofree f a = a :< f (Cofree f a)

scanCofree :: Functor f =>
              (b -> a -> b) -> b -> Cofree f a -> Cofree f b
scanCofree f a (x :< ns) = a' :< fmap (scanCofree f a') ns
  where
    a' = f a x

Let's say we have a simple imperative language Stmt with assignments and branching constructs

data Stmt a = SAssign Int -- id = ..
          | SSeq [Stmt a] -- smt0; smt1; ..
          | SIf (Stmt a) (Stmt a) -- branching
          deriving (Show)

And we want to track the variables that are in scope at each statement :

binds :: Stmt a -> Cofree (StmtF a) [Id]
binds = annotate f
  where
    f = \case
      SAssign i -> [i]
      _ -> []

s0 = SSeq [SAssign 0,SIf (SAssign 1) (SAssign 2)]

I cannot figure out why scanCofree (<>) mempty does not do anything (acts like the identity function):

> binds s0 
[] :< SSeqF [[0] :< SAssignF 0,[] :< SIfF ([1] :< SAssignF 1) ([2] :< SAssignF 2)]

> scanCofree (<>) mempty $ binds s0
[] :< SSeqF [[0] :< SAssignF 0,[] :< SIfF ([1] :< SAssignF 1) ([2] :< SAssignF 2)]

whereas I would like it to propagate the topmost [0] annotation to the levels below:

[] :< SSeqF [[0] :< SAssignF 0,[] :< SIfF ([0,1] :< SAssignF 1) ([0,2] :< SAssignF 2)]

Misc definitions:

-- | base functor of Stmt
data StmtF a x = SAssignF Id -- id = ...
          | SSeqF [x] -- smt0; smt1; ..
          | SIfF x x
          deriving (Eq, Show, Functor, Foldable, Traversable)
$(deriveShow1 ''StmtF)

type instance Base (Stmt a) = StmtF a

instance Recursive (Stmt a) where
  project = \case
    SAssign i -> SAssignF i
    SSeq xs -> SSeqF xs
    SIf a b -> SIfF a b

instance Corecursive (Stmt a) where
  embed = \case
    SAssignF i -> SAssign i
    SSeqF xs -> SSeq xs
    SIfF a b -> SIf a b

annotate :: Recursive t
         => (t -> a)
         -> t
         -> Cofree (Base t) a
annotate alg t = alg t :< fmap (annotate alg) (project t)
-- from micro-recursion-schemes

instance (Show1 f) => Show1 (Cofree f) where
  liftShowsPrec sp sl = go
    where
      goList = liftShowList sp sl
      go d (a :< as) = showParen (d > 5) $
        sp 6 a . showString " :< " . liftShowsPrec go goList 5 as

instance (Show1 f, Show a) => Show (Cofree f a) where
  showsPrec = showsPrec1
instance Functor f => Functor (Cofree f) where
  fmap f (a :< as) = f a :< fmap (fmap f) as
  b <$ (_ :< as) = b :< fmap (b <$) as

instance Foldable f => Foldable (Cofree f) where
  foldMap f = go where
    go (a :< as) = f a `mappend` foldMap go as
  {-# INLINE foldMap #-}
  length = go 0 where
    go s (_ :< as) = foldl' go (s + 1) as

instance Traversable f => Traversable (Cofree f) where
  traverse f = go where
    go (a :< as) = (:<) <$> f a <*> traverse go as


type family Base t :: Type -> Type

class Functor (Base t) => Recursive t where
  project :: t -> Base t t

  cata :: (Base t a -> a) -- ^ a (Base t)-algebra
       -> t               -- ^ fixed point
       -> a               -- ^ result
  cata f = c where c = f . fmap c . project

Solution

  • As @Li-yaoXia pointed out, you need a recursion point at each constructor that needs to propagate information at the lower levels of the tree. My initial definition of Stmt was incorrect because the `SAssign` constructor that models variable binding was a "leaf".

    This is a better definition that actually models nested variable scopes:

    data Stmt a = SAssign Id (Stmt a) -- id = ..; smt
              | SSeq [Stmt a] -- smt0; smt1; ..
              | SIf (Stmt a) (Stmt a) -- branching
              deriving (Show)
    
    -- | base functor of Stmt
    data StmtF a x = SAssignF Id x -- id = ...; smt
              | SSeqF [x] -- smt0; smt1; ..
              | SIfF x x
              deriving (Eq, Show, Functor, Foldable, Traversable)
    
    -- (adjust the Recursive and Corecursive instances as needed)
    
    
    instance Semigroup (Stmt a) where
      (<>) = undefined              -- we only need mempty
    instance Monoid (Stmt a) where
      mempty = SSeq []
    
    binds :: Stmt a -> Cofree (StmtF a) [Id]
    binds = annotate f
      where
        f = \case
          SAssign i _ -> [i]
          _ -> []
    
    s0 :: Stmt a
    s0 = SAssign 0 (SIf (SAssign 1 mempty) (SAssign 2 mempty))
    

    gives us at last

    λ>  scanCofree (<>) mempty $ binds s0
    [0] :< SAssignF 0 ([0] :< SIfF ([0,1] :< SAssignF 1 ([0,1] :< SSeqF [])) ([0,2] :< SAssignF 2 ([0,2] :< SSeqF [])))