Search code examples
haskellhaskell-lenslenses

Haskell Lens Tutorial with traverse


I'm trying to follow this tutorial: http://blog.jakubarnold.cz/2014/08/06/lens-tutorial-stab-traversal-part-2.html

I'm using the following code that I load into ghci:

{-# LANGUAGE RankNTypes, ScopedTypeVariables  #-}

import Control.Applicative
import Data.Functor.Identity
import Data.Traversable

-- Define Lens type.
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t 
type Lens' s a = Lens s s a a 

-- Lens view function. Omitting other functions for brevity.
view :: Lens s t a b -> s -> a
view ln x = getConst $ ln Const x

-- Tutorial sample data types
data User = User String [Post] deriving Show
data Post = Post String deriving Show

-- Tutorial sample data
john = User "John" $ map (Post) ["abc","def","xyz"]
albert = User "Albert" $ map (Post) ["ghi","jkl","mno"]
users = [john, albert]

-- A lens
posts :: Lens' User [Post]
posts fn (User n ps) = fmap (\newPosts -> User n newPosts) $ fn ps

From there, simple stuff like this works:

view posts john

However, when I try to do the next step it does not work:

view (traverse.posts) users

I get:

Could not deduce (Applicative f) arising from a use of ‘traverse’
from the context (Functor f)
  bound by a type expected by the context:
             Functor f => ([Post] -> f [Post]) -> [User] -> f [User]
  at <interactive>:58:1-27
Possible fix:
  add (Applicative f) to the context of
    a type expected by the context:
      Functor f => ([Post] -> f [Post]) -> [User] -> f [User]
In the first argument of ‘(.)’, namely ‘traverse’
In the first argument of ‘view’, namely ‘(traverse . posts)’
In the expression: view (traverse . posts) users

I see that Lens has a type constraint of Functor and traverse has a more constrained type constraint on f as an Applicative. Why exactly isn't this working and why did the blog tutorial suggest that it works?


Solution

  • view actually has a type that's less restrictive than Lens s t a b -> s -> a.

    If you drop the type signature, ghci will tell you the type for view

    :t view
    view :: ((a1 -> Const a1 b1) -> t -> Const a b) -> t -> a
    

    This is less restrictive because a Lens must be defined forall functors, while the first argument to view only needs to be defined for Const a1.

    If we rename the type variables based on the names from Lens and restrict a1 ~ a this signature will make more sense

    type Lens s t a b = forall f. Functor f =>
             (a -> f       b) -> s -> f       t 
    view :: ((a -> Const a b) -> s -> Const a t) -> s -> a
    view ln x = getConst $ ln Const x