haskellhaskell-lenstype-signature

What would be the type signature of a function that can be applied both like (& f %~) and (^. f) where operators are from Lens library?


I have a function that gets a value from a record and sets it to the same field in a different record.

What I want to do is understand how the mechanisms behind it works

setToPercent perc basedOn toSetOn = setStat
    where baseStat = basedOn ^. _func -- type is Maybe Float
          percentDelta = (\a -> perc * a) <$> baseStat -- Maybe Float
          setStat = toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta)

Where _func is defined as MyDataType -> Maybe Float (default function from record syntax.)

And it works just like I expect it to. What I want to know is _func's type signature.

I know here basedOn ^. _func, _func would have the type Getting (f0 Float) MyDataType (f0 Float)

and here toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta, _func would have the type ASetter Weapon Weapon (f0 Float) (f0 Float) where data Weapon and MyDataType are basically the same data type with identical constructors.

But I'm not sure what would be the overall type. As in what would be the function type if I were to generalize setToPercent w.r.t _func as in

setToPercent fnToApply perc basedOn toSetOn = setStat
    where baseStat = basedOn ^. fnToApply -- type is Maybe Float
          percentDelta = (\a -> perc * a) <$> baseStat -- Maybe Float
          setStat = toSetOn & fnToApply %~ (\a -> (+) <$> a <*> percentDelta)

Solution

  • Interesting question!

    I know here basedOn ^. _func, _func would have the type Getting (f0 Float) MyDataType (f0 Float)

    and here toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta, _func would have the type ASetter Weapon Weapon (f0 Float) (f0 Float)

    So, informally, _func is both a getter and a setter. Looking at the diagram on the lens homepage on Hackage or the one on the Glassery blogpost by Oleg Grenrus, the "least common ancestor" of getters and setters is lenses.

    The lens library proposes at least two representations of lenses, a polymorphic one Lens' Weapon (f0 Float) (the definition contains a forall) and a monomorphic one ALens' Weapon (f0 Float) (that replaces the forall with a concrete functor).

    Since we expect here _func to automatically specialize to ASetter and Getting, one straightforward way is to use the polymorphic one (this requires RankNTypes if _func is a parameter rather than a toplevel binding).

    _func :: Lens' Weapon (f0 Float)   -- Polymorphic
          -- specializes to
          :: Getting (f0 Float) Weapon (f0 Float)
          :: ASetter' Weapon (f0 Float)
    

    There is another way using the monomorphic version. I find this more idiomatic when _func is a function parameter, as it makes it easier for callers to pass in a suitable argument. However, we must then explicitly generalize _func when we actually use it.

    _func :: ALens' Weapon (f0 Float)   -- Monomorphic
    
    cloneLens _func :: Lens' Weapon (f0 Float)  -- Polymorphic again
    
    -- The ALens' type synonym usually appears in argument position
    myFun :: ALens' Foo Bar -> MyResult
    myFun _func = ...
    
    -- Example usage
    basedOn ^. cloneLens _func
    toSetOn & cloneLens _func %~ ...