I'm having trouble understanding all the nuances of the lens library in Haskell.
Suppose I have the following lens
activePlayer :: Lens' Game Player
activePlayer = lens get set
where
get (Game {_players = (index, seq) }) = S.index seq index
set g@(Game {_players = (index, seq) }) player =
g { _players = (index, S.update index player seq) }
Performing the following at the ghci prompt works with no problems:
> :t do{use (activePlayer); activePlayer.= undefined}
:: Control.Monad.State.Class.MonadState Game m => m ()
However when I try and parameterize it into a function I get the following error.
> :t \p -> do{use p; p.=undefined}
<interactive>:1:17:
Couldn't match type `Accessor a0 a0' with `Mutator b0'
Expected type: ASetter s0 s0 a0 b0
Actual type: Getting a0 s0 a0
In the first argument of `(.=)', namely `p'
In a stmt of a 'do' block: p .= undefined
In the expression:
do { use p;
p .= undefined }
It looks like p
is getting inferred as an Accessor
, when I want it to be inferred as a full Lens
. I tried forcing p
to be a Lens
with the following, but ghci complained about RankNTypes in Lens' a b
.
:t \p -> do{use p; p.=undefined} :: Lens' a b -> m c
I would greatly appreciate it if anyone could help me figure out why p
is being inferred the way it is, and how I can make it behave as a full Lens
.
The reason this is happening to you is that if you look at the type of Lens'
:
type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s
That is essentially giving you the ability to trade in a Lens'
at any one choice of Functor f
you want. However, use
wants to pick Accessor a
, while .=
wants to pick Mutator
.
If you are passed a Lens
and want to use it multiple times with different choices of functor, you'll need to either
a.) pass it with a higher rank type
{-# LANGUAGE RankNTypes #-}
foo :: MonadState a m => Lens' a b -> m ()
foo p = do
use p
p .= undefined
b.) cloneLens
it before you use it for reading and/or writing.
:t \p -> do{use (cloneLens p); cloneLens p.=undefined}
Using cloneLens
at each side will make a consistent choice of Functor, generating a fresh lens each time.
c.) Use the combinators from Control.Lens.Loupe
.
:t \p -> do gets (^# p); p #= undefined
These are designed to always make the same kind of choice as cloneLens
would.
In practice, it is better to use another approach, like
:t \p -> p %= \oldValue -> newValue
as this will support any Traversal
or Setter
, not just a Lens
, opening you up to more usecases.
If you need to get the value out for future calculations:
:t \p -> p %%= \oldValue -> (whatYouWantToReadFromIt, whatYouWantToWriteToIt)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With