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)
Interesting question!
I know here
basedOn ^. _func
,_func
would have the typeGetting (f0 Float) MyDataType (f0 Float)
and here
toSetOn & _func %~ (\a -> (+) <$> a <*> percentDelta
,_func
would have the typeASetter 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 %~ ...