First time using lens
. set
and over
went easy enough and I thought it would be simple with view
: Use the same scheme to reference the inner part, but don't supply a new value or function. But Noooo. tst3 below gives the error below the code
. Anyone know what's going on?
-- Testing lenses
tst1 = set (inner . ix 0 . w) 9 outer
tst2 = over (inner . ix 0 . w) (+2) outer
tst3 = view (inner . ix 0 . w) outer -- this line errors out
* No instance for (Monoid Int) arising from a use of `ix'
* In the first argument of `(.)', namely `ix 0'
In the second argument of `(.)', namely `ix 0 . w'
In the first argument of `view', namely `(inner . ix 0 . w)'
Here is some pared down code illustrating.
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
import Control.Lens
data Inner = Inner
{ _w :: Int
} deriving (Show, Eq)
data Outer = Outer
{ _inner :: [Inner]
} deriving Show
outer = Outer
[
Inner 0,
Inner 1,
Inner 2
]
makeLenses ''Inner
makeLenses ''Outer
tst1 = set (inner.ix 0.w) 999 outer
tst2 = over (inner.ix 0.w) (+77) outer
tst3 = view (inner.ix 0.w) outer -- this errors out
I've read up on view
and the simple examples look like mine, as in,
>>> let atom = Atom { _element = "C", _point = Point { _x = 1.0, _y = 2.0 } }
>>> view (point . x) atom
1.0
though I haven't found an example that indexes an inner structure with ix.
The problem is that ix 0
is a traversal, not a lens, so instead of focusing on the 0th element, it focuses on the collection of all elements that are the 0th element. Why does it do this? Well, assuming _inner
is a list of type [Inner]
, usually there's only one element that is the 0th element, but sometimes (if the list is empty), there are no elements that are the 0th element. Having ix 0
be a traversal accounts for this possibility.
Since ix 0
is a traversal, it "poisons" the whole optic inner . ix 0 . w
. Even if inner
and w
are lenses, the composition with ix 0
is "only" a traversal.
Now, there's no problem with "setting" or "overing" with a traversal. If there's no 0th element, the operation just doesn't do anything. On the other hand, there is a bit of a problem with viewing such a traversal. If you expect to get an element, you might not get one.
The error message arises because view
tries to handle traversals by assuming the result is a Monoid
. This allows it to combine zero, one, or multiple results from the traversal into a single return value of the same type. This functionality is kind of esoteric, which makes the error message pretty confusing, but you get used to seeing it.
There are several things you can do. You can replace view
with a special view operators. The usual view operator ^.
is similar to view
, so it generates the same error message:
-- parentheses are optional here, but included for clarity
tst4 = outer ^. (inner . ix 0 . w)
But the operators ^..
, ^?
and ^?!
will all type check:
tst5 = outer ^.. (inner . ix 0 . w)
tst6 = outer ^? (inner . ix 0 . w)
tst7 = outer ^?! (inner . ix 0 . w)
The first (^..)
returns all (zero or more) results of the traversal as a list. The second (^?)
returns the first result as a Maybe
, using Nothing
to indicate there were no results. The last (^?!)
returns the first result directly, generating an error if there are zero results (like using head
on an empty list).