Using a lens twice
Asked Answered
S

3

7

I'm struggling with using the lens library for a particular problem. I'm trying to pass

  1. an updated data structure
  2. a lens focussed on part of that updated structure

to another function, g. I pass both the lens and the data structure because g needs some shared information from the data structure as well as a piece of information. (If it helps, the data structure contains information on a joint probability distribution, but g only works on either marginal and needs to know which marginal I'm looking at. The only difference between the two marginals is their mean with the rest of their definition being shared in the data structure).

My first attempt looked like this

f :: Functor f => Params -> ((Double -> f Double) -> Params -> f Params) -> a
f p l = g (l %~ upd $ p) l
  where upd = ...

g p x = go p p^.x

but that fails during compilation because f gets inferred as being Identity for the update and Const Double for the getter.

What's the best way to accomplish what I want to do? I can imagine being able to do one of the following:

  1. make a copy of the lens so that the type inference can be different in each case
  2. rather than passing the updated structure and the lens, I pass the original structure and a lens which returns a modified value (if I only want to update the part of the structure that the lens looks at).
  3. making a better design choice for my functions/data structure
  4. something completely different

Thanks for any help!

Sebrinasebum answered 2/5, 2014 at 8:49 Comment(0)
E
10

András Kovács answer shows how to achieve this with RankNTypes. If you wish to avoid RankNTypes, then you can use ALens and cloneLens:

f :: a -> ALens' a Int -> (Int, a)
f a l = (newvalue, a & cloneLens l .~ newvalue)
  where oldvalue = a^.cloneLens l
        newvalue = if oldvalue == 0 then 0 else oldvalue - 1

Control.Lens.Loupe provides operators and functions that work on ALens instead of Lens.

Note that in many cases, you should also be able to use <<%~, which is like %~ but also returns the old value, or <%~, which returns the new value:

f :: a -> LensLike' ((,) Int) a Int -> (Int, a)
f a l = a & l <%~ g
  where g oldvalue = if oldvalue == 0 then 0 else oldvalue - 1

This has the advantage that it can also work with Isos or sometimes also with Traversals (when the target type is a Monoid).

Ethelda answered 2/5, 2014 at 13:18 Comment(0)
H
7

You want your type signature to look like this:

f :: Params -> Lens Params Params Double Double -> ...
-- alternatively, instead of the long Lens form you can write
-- Lens' Params Double

This is not equivalent to what you wrote out in the signature, because the functor parameter is quantified inside Lens:

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)

The correct signature translates to :

f :: Params -> (forall f. Functor f => (Double -> f Double) -> Params -> f Params) -> ...

This prevents the compiler from unifying the different f parameters of different lens usages, i. e. you can use the lens polymorphically. Note that you need the RankNTypes or Rank2Types GHC extension in order to be able to write out the signature.

Homesick answered 2/5, 2014 at 10:31 Comment(0)
L
2

Benno gave the best general purpose answer.

There is two other options, however, which I offer here for completeness.

1.)

There are several Loupe combinators in Lens.

http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Loupe.html

They all have names that involve #.

^# and #%= both take ALens which is a lens instantiated at a particular concrete choice of functor.

This can be useful if you need to pass around lists of lenses, or if you really really need multiple passes.

2.)

Another option, and my preferred tactic, is to figure out how to do both operations a the same time.

Here you are modifying, but want the value you just set. Well, yes can give you that by using <%~ instead of %~.

Now you only instantiate the lens at one choice of functor and your code gets faster.

Lutes answered 2/5, 2014 at 17:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.