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

Functoriality law is a free theorem #140

Open
masaeedu opened this issue Apr 16, 2020 · 12 comments
Open

Functoriality law is a free theorem #140

masaeedu opened this issue Apr 16, 2020 · 12 comments

Comments

@masaeedu
Copy link

masaeedu commented Apr 16, 2020

The law labeled "functoriality" in the Semialign class is a free theorem.

Here is the law as written:

f1, f2 :: Semialign f => (a -> c) -> (b -> d) -> f a -> f b -> f (These c d)
f1 f g x y = align (f <$> x) (g <$> y)
f2 f g x y = bimap f g <$> align x y

-- "Functoriality"
-- f1 = f2

Uncurry the arguments:

f1, f2 :: Semialign f => (a -> c) -> (b -> d) -> (f a, f b) -> f (These c d)
f1 f g (x, y) = align (f <$> x) (g <$> y)
f2 f g (x, y) = bimap f g <$> align x y

Eta reduce:

f1, f2 :: Semialign f => (a -> c) -> (b -> d) -> (f a, f b) -> f (These c d)
f1 f g = uncurry align . bimap (fmap f) (fmap g)
f2 f g = fmap (bimap f g) . uncurry align

Explicitly write composite functors:

bimapBiff :: (Bifunctor t, Functor f, Functor g) => (a -> c) -> (b -> d) -> t (f a) (g b) -> t (f c) (g d)
bimapBiff f g = bimap (fmap f) (fmap g)

bimapTannen :: (Bifunctor t, Functor f) => (a -> c) -> (b -> d) -> f (t a b) -> f (t c d)
bimapTannen f g = fmap (bimap f g)

f1, f2 :: Semialign f => (a -> c) -> (b -> d) -> (f a, f b) -> f (These c d)
f1 f g = uncurry align . bimapBiff f g
f2 f g = bimapTannen f g . uncurry align

In other words, the law simply demands that align :: Biff (,) f f a b -> Tannen f These a b is a natural transformation between the bifunctors Biff (,) f f and Tannen f These.

This is a free theorem: every parametrically polymorphic function of the type foo :: forall a b. T a b -> U a b, where T and U are bifunctors, is a natural transformation between those bifunctors.

Like any other free theorem, it can be violated by doing naughty stuff with partiality and laziness in Haskell, but if we ignore those pathological cases the law is inviolable.

@phadej
Copy link
Collaborator

phadej commented Apr 16, 2020

Or with GADTs.

@phadej
Copy link
Collaborator

phadej commented Apr 16, 2020

More wordly:

The composition Functor law fmap (f . g) = fmap f . fmap g is similarly implied by fmap id = id and free theorem, but there's still point in stating that law. It can be broken without partiality and laziness, GADTs can be non-parameteric and non-natural, and therefore free theorems won't hold.

@masaeedu
Copy link
Author

@phadej Can you show an example of a GADT that violates the law without relying on partiality and laziness? There is a long discussion on a similar topic here: https://stackoverflow.com/a/60384830, and I remain unconvinced that there is a way to break parametricity merely with the use of existential types.

@masaeedu
Copy link
Author

Yes, Mag is indeed mentioned in that blog post. What about it?

@phadej
Copy link
Collaborator

phadej commented Apr 17, 2020

Sorry, I should asked the following right in the beginning:

What is the issue? What one need to do to resolve it?


Mag is an example of a type which breaks parametricity.

@masaeedu
Copy link
Author

The issue is that the functoriality law is implied by the laws of functors and the type system, so in most situations it is not a law the user needs to worry about when writing an instance, as they do for the other laws. It would be good to distinguish this in the documentation, as is done for example in the documentation for the selective functor library: https://hackage.haskell.org/package/selective-0.4.1/docs/Control-Selective.html.

Can you flesh out what you mean regarding Mag? I still don't see a way in which that type violates parametricity.

@masaeedu
Copy link
Author

If I were to guess I think you're pointing to the fact that fmap id /= id, but it's also the case that fmap (f . g) /= fmap f . fmap g for that type, so I don't really understand what this is supposed to prove about violating free theorems.

The statement about parametricity that's relevant to the issue we're discussing is that for any two functors F and G, a function of the type forall a. F a -> G a is automatically a natural transformation between them. If you have an example of a GADT that violates this, then obviously this statement is wrong and I need to reconsider the utility of free theorems, but I haven't seen one thus far.

@phadej
Copy link
Collaborator

phadej commented Apr 17, 2020

It would be good to distinguish this in the documentation,

Please make a PR.

@phadej
Copy link
Collaborator

phadej commented Apr 17, 2020

Note: I include the naturality law, because in Haskell it (might be often / always is) true. But in more general setting it wouldn't.

For Functor specifying fmap (f . g) = fmap f . fmap g is not necessary either. You cannot break that law with parameteric type, without also breaking fmap id = id one too.

@masaeedu
Copy link
Author

masaeedu commented Apr 17, 2020

For Functor specifying fmap (f . g) = fmap f . fmap g is not necessary either. You cannot break that law with parameteric type, without also breaking fmap id = id one too.

That's what I'm talking about. If the fmap id = id law held but fmap (f . g) = fmap f . fmap g didn't then this would illustrate a violation of the free theorem for fmap. However neither law holds for that type, so I don't see how this is relevant to a discussion of free theorems and whether or not they hold in the presence of GADTs.

@phadej
Copy link
Collaborator

phadej commented Apr 17, 2020

Screenshot from 2020-04-17 15-48-37

There is no note that the latter composition fmap (f . g) = fmap f . fmap g holds for free if fmap id holds. Even there are blogs post about that (e.g. https://www.schoolofhaskell.com/user/edwardk/snippets/fmap)

I argue that there is really no benefit in omitting stating naturality laws. It might make sense to mention that you need to do suspicious things to break them, but they should be in the documentation.

I still don't undestand what and how you try to improve. Please make a PR = concrete change proposal.

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

No branches or pull requests

2 participants