Reputation: 568
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
Upvotes: 2
Views: 186
Reputation: 120711
Lens'
is under the hood universally quantified
type Lens' s a = ∀ f . Functor f => (a -> f a) -> s -> f s
So the type of [v,w,x]
would have to be
[∀ f . Functor f => (AType -> f AType) -> Thing -> f Thing]
– 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 is ReifiedLens
. With it you can write
transform a b =
let transformVorWorX :: ReifiedLens' Thing AType -> AType
transformVorWorX (Lens lens) =
let vwx = view lens a
vwx' = view lens b
in f vwx vwx'
in foldl (\a' lens -> set (runLens lens) (transformVorWorX lens) a') a
[Lens v, Lens w, Lens x]
This 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
within transformVorWorX
:
transform a b =
let transformVorWorX :: ALens' Thing AType -> AType
transformVorWorX lens =
let vwx = view (cloneLens lens) a
vwx' = view (cloneLens lens) b
in f vwx vwx'
in foldl (\a' lens -> set (cloneLens lens) (transformVorWorX lens) a') a [v,w,x]
The reason this works is that, like ReifiedLens
– but unlike Lens
– ALens'
has no quantor. It is just a concrete instantiation of the Functor f
, thus it doesn't require impredicativity but you can directly use v
, w
and x
in a [ALens' Thing AType]
list. The functor instance is cleverly chosen to not lose any of the generality, hence cloneLens
is able to give you back a full, universally-quantised lens.
Rather than cloning it, you can also use the ^#
operator, which directly views an ALens
, and storing
instead of set
:
transform a b =
let transformVorWorX :: ALens' Thing AType -> AType
transformVorWorX lens =
let vwx = a^#lens
vwx' = b^#lens
in f vwx vwx'
in foldl (\a' lens -> storing lens (transformVorWorX lens) a') a [v,w,x]
If we condense that to a less verbose form, it looks quite good IMO:
transform a b = foldl updater a [v,w,x]
where updater a' lens = a' & lens #~ f (a^#lens) (b^#lens)
Let me also suggest an entirely different solution: you define a single optic that accesses all the values of interest, namely
vwx :: Traversal' Thing AType
vwx f (Thing в ш к otherVals)
= (\в' ш' к' -> Thing в' ш' к' otherVals)
<$> f в <*> f ш <*> f к
Instead than defining this yourself, you can also let the Template Haskell helper do it for you:
makeLensesFor [("_v", "vwx"), ("_w", "vwx"), ("_x", "vwx")] ''Thing
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:
transform a b = ...
where tfmedVals = zipWith f (a^..vwx) (b^..vwx)
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
transform a b = a & partsOf vwx .~ zipWith f (a^..vwx) (b^..vwx)
Upvotes: 7