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.
CodePudding user response:
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).
CodePudding user response:
ix 0
doesn't produce a lens, but a traversal.1
Informally, a lens is a "path" that will definitively reach a single value (if you follow it within a hypothetical larger value). A traversal is a path to zero or more values. You can set
or view
the single target of a lens. And you can set
the zero or more targets of a traversal (this simply updates all of them that are present, which is a no-op if there are zero present). But view
ing the targets of a traversal is less straightforward.
If view
simply took a traversal, and an outer structure, and gave you the target value, then it would have a problem. If there are multiple targets, how should it decide which to return? And if there are zero targets, it can't return anything; it would have to be partial. What it needs is a way of condensing zero-or-more values into a single value, so it can return that. And the Monoid
class provides exactly the facilities to do that; mempty
for if there aren't any targets at all, and <>
to condense multiple values to a single one. So view
with a traversal2 actually requires a Monoid
constraint on the returned type, and that's why you're getting the complaint about No instance for (Monoid Int) arising from a use of `ix'
.
And in case it isn't clear, you can often compose (with .
) different types of optics (the general term for "lens-ish things", including lenses, traversals, and several others). But the result has the capabilities of the least capable of the two inputs. So even though your inner
and w
are full lenses, composing them with a traversal produced by ix
results in a traversal, not a lens.
But in this case you know that you're using ix
. The specific kind of traversals ix
makes end up having zero or one target, rather than the zero or more targets that traversals have in general. So you could use preview
instead of view
; for a traversal it will produce a Maybe
containing Just
the first target of the traversal, or Nothing
if there weren't any. A Maybe
is exactly what the type system deems appropriate here, since ix
can't guarantee there will be a target value, but there won't be more than one.
In my experience, when I try to view
something and get this Monoid
instance error, it almost always means I have an optic that can't guarantee a result and I should actually be using preview
to get a Maybe
.
Simple example:
λ view (ix 1) [True, False]
<interactive>:16:7: error:
• No instance for (Monoid Bool) arising from a use of ‘ix’
• In the first argument of ‘view’, namely ‘(ix 1)’
In the expression: view (ix 1) [True, False]
In an equation for ‘it’: it = view (ix 1) [True, False]
λ preview (ix 1) [True, False]
Just False
it :: Maybe Bool
λ preview (ix 1) [True]
Nothing
it :: Maybe Bool
1 ix
can't return lenses, because it's supposed to take an index and then represent a "path" into any indexable structure (that can be indexed by the type of index it was given, at least). In ix 0
, ix
hasn't seen the particular structure you're going to use it on yet, so there's no way it can guarantee there is a value at that index; you could even use let i = ix 0
and then use i
multiple times to peek into different structures!
2 More technically, view
is supported by traversals because it's supported by folds, and all traversals are folds. A fold knows how to access zero-or-more targets, but unlike a traversal it isn't a "path"; it just accesses the values with no knowledge about their context in the structure (other than a canonical order).
There's a handy but confusing diagram on the main hackage page for the lens
package, which attempts to describe how the major functionality is "inherited" by various flavours of optics. It's a little overwhelming... but less so than the full documentation scattered over many different modules.
You should be able to see that in the top right we have Setter
providing set
and over
, which is inherited by Traversal
. Whereas in the top left we have view
with a Monoid
constraint, that is inherited by Traversal
. In the mid-left we have Getter
having a version of view
without the Monoid
constraint, which is inherited by Lens
but not by Traversal
.