3

After reading (and implementing) part of http://blog.sumtypeofway.com/recursion-schemes-part-2/ I still wonder how the types in the cata function work. The cata function is defined as:

mystery :: Functor f => (f a -> a) -> Term f -> a
mystery f = f . fmap (mystery f) . unTerm

I have something like Term Expr. After unpacking I get Expr (Term Expr). The algebra (f) is defined e.g. as f :: Expr Int -> Int. I know I could call the following easily:

x = Literal "literal" :: Expr a
f :: Expr Int -> Int
f x :: Int

I can also imagine:

x = Literal "literal" :: Expr (Term Expr)
f :: Expr a -> Int
f x :: Int

But the following I suppose wouldn't work:

x = Literal "literal" :: Expr (Term Expr)
f :: Expr Int -> Int
f x :: ???

However, I still don't get how it works in the cata function - how do I get from Expr (Term Expr) to Expr a. I understand that the values do work, but I just don't get the types - what happens in the leaves of the tree? This is indeed a mystery...

Edit: I'll try to state more clearly what I don't understand.

Mentally, the cata seems to work this way:

  • Apply fmap f to leaves.
  • Now I have Expr Int and I can call fmap f to the node I have and get way up.

It doesn't obviously work this way as I am applying fmap (cata f). However, ultimately the function f gets called with Expr Int as an argument (in the leaves). How was this type produced from Expr (Term Expr) that it was before?

Robin Green
  • 32,079
  • 16
  • 104
  • 187
ondra
  • 9,122
  • 1
  • 25
  • 34
  • You can't really define a meaningful `f :: Expr a -> Int`. – chi Nov 12 '15 at 11:53
  • Sure you can: `f (Literal str) = length str` – ondra Nov 12 '15 at 11:54
  • 1
    seems your `f` is not a total function anymore ... what about `f (Paren ...)`? – Random Dev Nov 12 '15 at 11:55
  • That's not very meaningful: what about all the other cases? There's no way to exploit the generic `a` to affect the `Int` result. – chi Nov 12 '15 at 11:55
  • That's the trick with catamorphism - it looks as if somewhere inside the function in the leaves there was something like `fmap (undefined :: a -> Int) x`. Then you can defined `f (Parens a b) = a + b` because the `a` and `b` are Ints. – ondra Nov 12 '15 at 11:57
  • what happens with the leaves depends on what `f` has to say to it - `mystery` is there to handle the *folding* for you – Random Dev Nov 12 '15 at 11:58
  • Carsten, that's right. What I don't understand is how the leaves get converted from type `Expr (Term Expr)` into `Expr a`. – ondra Nov 12 '15 at 11:59
  • 1
    No, you can't write `f (Parens a b) = a+b` unless `f :: Expr Int -> Int`. The point is that `a,b` have the type which is argument to `Expr`. (BTW, `Paren` takes only one argument, but let's ignore that -- that's not the point) – chi Nov 12 '15 at 11:59
  • The point is that f is `f :: Expr Int -> Int`. That's why it's a `mystery`. – ondra Nov 12 '15 at 12:00
  • I really can't understand the question, then. My point is: if `f :: Expr Int -> Int`, then `mystery f :: Term Expr -> Int` will do all the folding. If `f :: Expr a -> Int`, then `f` is constrained by parametricity to be practically useless. We _can_ still use `mystery f`, but in practice it will be almost useless. – chi Nov 12 '15 at 12:04
  • right now it's hard to formulate as you seem to have strayed from the `newtype Term f = In { out :: f (Term f) }` definition used in your linked article. If you add the missing definitions and give a correct term then I can show you how it just unfolds using `mystery` - but really you can do yourself - take a paper right it down and then just *reduce* ;) – Random Dev Nov 12 '15 at 12:04
  • By the way, the leaves don't get converted from `Expr (Term Expr)` into `Expr a` -- what makes you think that's the case? – chi Nov 12 '15 at 12:05
  • @chi I think OP has trouble seeing where the `f` really *happens* finally – Random Dev Nov 12 '15 at 12:06
  • I tried to edit the question to make it more clear where I have the problem. Carsten - yes, that's probably the trouble I have :) – ondra Nov 12 '15 at 12:10

1 Answers1

2

This is how cata operates on leaves.

Assume f :: Expr Int -> Int. Then:

cata f :: Term Expr -> Int
fmap (cata f) :: Expr (Term Expr) -> Expr Int

Now, for any function g :: a -> b, we have

fmap g :: Expr a -> Expr b
fmap g (Literal n) = Literal n
...

So, on literals, g is immaterial. This means that, choosing a ~ Term Expr, b ~ Int and g = cata f we have

fmap (cata f) (Literal n) = Literal n  :: Term Int
f (fmap (cata f) (Literal n)) = f (Literal n) :: Int

So, roughly, on leaves fmap (cata f) is a no-op, but it changes the type from Expr (Term Expr) to Expr Int. This is a trivial transformation since Literal n :: Expr a for any a.

chi
  • 111,837
  • 3
  • 133
  • 218
  • The last sentence is what I have been searching for. I got all the steps above, but this was missing. Thanks. – ondra Nov 12 '15 at 16:29