Search code examples
haskellhaskell-lenslenses

Using a lens twice


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!


Solution

  • 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).