Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell, Lenses, Getters, and Setters

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.

like image 206
Dwilson Avatar asked Dec 17 '13 05:12

Dwilson


1 Answers

What is happening?

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.

How to do what you asked

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.

How to do what you probably want

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)
like image 83
Edward Kmett Avatar answered Oct 05 '22 00:10

Edward Kmett