Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalise hoistCofree #131

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Generalise hoistCofree #131

wants to merge 1 commit into from

Conversation

neongreen
Copy link

See aelve/haskell-issues#5. Proposed usecase:

import qualified Data.Foldable as F
braid :: Monad m => Cofree m a -> [m a]
braid = F.toList . hoistCofree (Identity . join) . return

@neongreen
Copy link
Author

I have fixed the build.

@fizruk
Copy link
Collaborator

fizruk commented Apr 8, 2016

First of all your use case does not compile and it's unclear what was meant there.

Anyway, below I try to give an exploration of why you don't want to change the type of hoistCofree.
I hope that I understand this stuff correctly, but if that's not the case I would like someone to correct me.

hoistCofree is supposed to reflect the fact that natural transformation f ~> g automatically gives rise to a natural transformation Cofree f ~> Cofree g (in a unique way!). In fact hoistCofree is a homomorphism:

hoistCofree id == id
hoistCofree f . hoistCofree g == hoistCofree (f . g)

With the original type you have these properties for all f and g.
Now when you change the type, you lose them. Consider these functions:

f :: (Num a, Functor f, Functor g) => f (Cofree g a) -> f (Cofree g a)
f = fmap (fmap (*2))

g :: (Num a, Functor f, Functor g) => f (Cofree g a) -> f (Cofree g a)
g = fmap (fmap (+1))

f . g == fmap (fmap (\x -> (x + 1) * 2))

Note that hoistFree f, hoistFree g and hoistFree (f . g) are all illegal with the original type. But they are allowed in your version. Now let's consider a simple cofree comonad:

type Stream = Cofree Identity

-- a stream of natural numbers
nats :: Stream
nats = 1 :< Identity (2 :< Identity (3 :< ...))

-- let's imagine we have this nice notation for streams:
nats = [1, 2, 3, ...]

Let's try on your version of hoistCofree:

hoistCofree g nats
= hoistCofree g (1 :< Identity [2, 3, ...])
= 1 :< g (hoistCofree g <$> Identity [2, 3, ...])
= 1 :< g (hoistCofree g [2, 3, ...])
= 1 :< g (2 :< g (hoistCofree f [3, ...]))
= [1, g 2, g (g 3), g (g (g 4), ...]
= [1, 2+1, 3+1+1, 4+1+1+1, ...]
= [1, 3, 5, 7, ...]

hoistCofree f [1, 3, 5, 7, ...]
= [1, f 3, f (f 5), f (f (f 7)), ...]
= [1, 3*2, 5*2*2, 7*2*2*2, ...]
= [1, 6, 20, 56]

hoistCofree (f . g) [1, 2, 3, ...]
= [1, f (g 2), f (g (f (g 3))), ... ]
= [1, (2+1)*2, ((3+1)*2)+1)*2, ...]
= [1, 6, 18, 46, ...]

As you can see for these choices of f and g your version does not satisfy this property:

hoistCofree f . hoistCofree g == hoistCofree (f . g)

So as with other abstractions when you gain more power, you lose some properties.
I believe hoistCofree need to have those, so if you still need that more general function, you should add it by a different name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants