Lenses can be composed like any ordinary function. We have:
Lens' a b = forall f . Functor f => (b -> f b) -> a -> f a
Now consider this example:
(.) :: Lens' Config Foo -> Lens' Foo String -> Lens' Config String
Expanding we get:
(.) :: (forall f. Functor f => (Foo -> f Foo) -> Config -> f Config)
-> (forall f. Functor f => (String -> f String) -> Foo -> f Foo)
-> (forall f. Functor f => (String -> f String) -> Config -> f Config)
And the type of function composition is:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
Which lacks any universal quantification and typeclass constraints. Now my question is, how are these two features treated by the compiler/type-checker so that the function composition operator can be used for composing lenses?
My guess is that it is OK to have functions universally quantified and typeclass constraints, as long as these match for the two functions being composed.
Why don't we see what happens? Consider the following values:
The type of
fooandbarwill be expanded to:Note that I left out the
forall f.part because it's implicit. Also, I changed the name offtogforbarto show that it's different from thefforfoo.Anyway, we'll first apply
(.)tofoo:Thus,
(.) foohas the typeFunctor f => (a -> B -> f B) -> a -> A -> f A. As you can see, theFunctorconstraint is simply copied as is.Now, we apply
(.) footobar:Thus,
(.) foo barhas the typeFunctor g => (C -> g C) -> A -> g Awhich means that it's aLens' A C. As you can seeFunctor fis the same asFunctor gwhich is why everything works out.