Search code examples
haskelllenses

Creating a lens(like) for list


I'm trying to create lenses for the following data structure. I'm using lens-family.

data Tree = Tree {
        _text :: String,
        _subtrees :: [Tree]
    } deriving (Show,Eq,Read,Generic)

I'd like to avoid using Template Haskell for various reasons. For one, it seems it's not available for my version of ghc (7.8.3), that's another (out of scope) problem.

Creating lenses for the record wasn't to hard.

text :: Lens' Tree String
text f (Tree text' subtrees') =
  (\text'' -> Tree text'' subtrees') `fmap` (f text')

subtrees :: Lens' Tree [Tree]
subtrees f (Tree text' subtrees') =
  (\subtrees'' -> Tree text' subtrees'') `fmap` (f subtrees')

But it seems that lens-family doesn't have default lenses for lists. I assume this is possible. There are for the lens package. These are my failed attempts:

import Lens.Family2
import Lens.Family2.Unchecked
-- List lenses
_last :: Lens [a] [a'] a a'
_last f l =
  -- first try
  --lens getter setter
  -- second try
  (\el -> (init l) ++ el) `fmap`(f (last l))
  where
    getter = last
    setter l el = l ++ el

They both get an error akin to this one:

Could not deduce (a' ~ [a'])
    from the context (Functor f)
      bound by the type signature for
                 _last :: Functor f => LensLike f [a] [a'] a a'
      at editor.hs:22:10-27
      ‘a'’ is a rigid type variable bound by
           the type signature for
             _last :: Functor f => LensLike f [a] [a'] a a'
           at editor.hs:22:10
    Expected type: f [a']
      Actual type: f a'
    Relevant bindings include
      f :: a -> f a' (bound at editor.hs:23:7)
      _last :: LensLike f [a] [a'] a a' (bound at editor.hs:23:1)
    In the second argument of ‘fmap’, namely ‘(f (last l))’
    In the expression: (\ el -> (init l) ++ el) `fmap` (f (last l))

How can I define the _last lens?

Edit: Here's a version that builds:

_last f l = (\el -> (init l) ++ [el]) `fmap`(f (last l))

Although, as David/Daniel points out, _last should be a Traversal, not a lens.


Solution

  • You are missing square brackets around el ((++) takes a list for both arguments). Your first try should work if you put them in. Your type is also too general. Lists aren't heterogenous in Haskell, so they can only contain values of one type.

    Also, as Daniel Wagner says, this can't be a true lens because it is partial. The lens documentation you linked to is out of date. The current lens library has _last as a Traversal which avoids this problem because a Traversal can have 0 or more targets, as opposed to a lens which must have exactly 1.