This is a version of some code I have been working on, with the specifics removed. Hopefully it's clear what I'm trying to do, but if not I can clarify. I'm quite new to using lenses, and the complicated types involved often make them seem more trouble than they're worth.
-- some data type
type AType = ...
data Thing = Th { _v, _w, _x :: AType, _otherStuff :: ... }
makeLenses ''Thing
-- some operation that can be performed on corresponding x or w or v values in two Things.
f :: AType -> AType -> AType
f = ...
-- apply f to the corresponding v, and w, and x values in a and b; then store each result in the respective field of a, leaving other fields untouched.
transform :: Thing -> Thing -> Thing
transform a b =
let transformVorWorX :: Lens' Thing AType -> AType
transformVorWorX lens =
let vwx = view lens a
vwx' = view lens b
in f vwx vwx'
in foldl (\a' lens -> set lens (transformVorWorX lens) a') a [v,w,x]
When I compile, ghc spits out
Could not deduce (f ~ Identity)
from the context (Functor f)
bound by a type expected by the context:
Functor f => (AType -> f AType) -> Thing -> f Thing
at ...
‘f’ is a rigid type variable bound by
a type expected by the context:
Functor f => (AType -> f AType) -> Thing -> f Thing
at ...
Expected type: (AType -> f AType) -> Thing -> f Thing
Actual type: ASetter Thing Thing AType AType
In the first argument of ‘transformVorWorX’, namely ‘lens’
In the second argument of ‘set’, namely ‘(transformVorWorX lens)’
Why doesn't this code work? I found that replacing lens
with cloneLens lens
lets it compile, but I'm not sure why, and this feels very ugly - is there a more elegant way to do what I want, that is perhaps more general?
Many thanks
Lens'
is under the hood universally quantifiedSo the type of
[v,w,x]
would have to be– with a quantor inside the list. Such types are called impredicative types, and Haskell doesn't support them. (A GHC extension with that name has been around for a while, but it doesn't work.) [Edit: work on impredicatives is ongoing as of 2021.]
There's a relatively easy way that always works to get around this: hide the quantor in a
newtype
. In the lens library, this isReifiedLens
. With it you can writeThis code has exactly the runtime behaviour of the one you envisioned, but it's clearly pretty ugly.
The alternative solution you've discovered yourself does effectively the same thing too but is rather nicer. To see what's actually going on there, I'll put the
cloneLens
withintransformVorWorX
:The reason this works is that, like
ReifiedLens
– but unlikeLens
–ALens'
has no quantor. It is just a concrete instantiation of theFunctor f
, thus it doesn't require impredicativity but you can directly usev
,w
andx
in a[ALens' Thing AType]
list. The functor instance is cleverly chosen to not lose any of the generality, hencecloneLens
is able to give you back a full, universally-quantised lens.Rather than cloning it, you can also use the
^#
operator, which directly views anALens
, andstoring
instead ofset
:If we condense that to a less verbose form, it looks quite good IMO:
Let me also suggest an entirely different solution: you define a single optic that accesses all the values of interest, namely
Instead than defining this yourself, you can also let the Template Haskell helper do it for you:
Now, a traversal can be used as a simple fold, and thus to get a list of all the values. You can then use a standard zip to perform the transformation of the elements:
To put that list of modified values back in a container, the library has also a function:
partsOf
. And with that, your entire transformation boils down to