I am trying to learn about lenses by implementing it in Haskell. I have implemented the view
combinator as follows:
{-# LANGUAGE RankNTypes #-}
import Control.Applicative
import Data.Traversable
type Lens s a = Functor f => (a -> f a) -> s -> f s
view :: Lens s a -> s -> a
view lens = getConst . lens Const
However when I try to use it in conjunction with traverse
I get the following error message:
Prelude> :load Lens.hs
[1 of 1] Compiling Main ( Lens.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t view traverse
<interactive>:1:6:
Could not deduce (Applicative f) arising from a use of ‘traverse’
from the context (Traversable t)
bound by the inferred type of it :: Traversable t => t a -> a
at Top level
or from (Functor f)
bound by a type expected by the context:
Functor f => (a -> f a) -> t a -> f (t a)
at <interactive>:1:1-13
Possible fix:
add (Applicative f) to the context of
a type expected by the context:
Functor f => (a -> f a) -> t a -> f (t a)
or the inferred type of it :: Traversable t => t a -> a
In the first argument of ‘view’, namely ‘traverse’
In the expression: view traverse
Unfortunately, I don't understand this error message. Please explain what it means and how I may fix it.
As the other answers already explain, the issue is that view
expects something that works for any Functor f
, but traverse
only works if f
is also Applicative
(and there are functors which are not applicative).
In lens
, the problem is solved by making the type of view
not take a Rank2
argument (in fact, most functions in lens don't use the Lens type synonym, they always use something weaker). For your function, observe that view
only ever uses f ~ Const
. This is why you can change the type signature to:
view :: ((a -> Const a a) -> s -> Const a s) -> s -> a
The implementation can stay the same, but now view
also works on traverse
:
view traverse :: (Traversable t, Monoid a) => t a -> a
Note the extra Monoid
constraint. This constraint appears because if you set f ~ Const a
in traverse :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
, you need an instance Applicative (Const a)
. That instance has a Monoid
constraint on a
though. And this also makes sense, because the traversable might be empty or contain more than one element, so you need mempty
and mappend
.