Updatable analogue of Distributive functors.
See README.md
Logistic is to setters as Distributive is to getters
Distributive functors are containers where getters can be enumerated as their own types.
This is the definition of the Distributive
class:
class Functor g => Distributive g where
distribute :: Functor f => f (g a) -> g (f a)
One easy-to-understand instance is Complex
.
data Complex a = !a :+ !a
realPart :: Complex a -> a
realPart (x :+ _) = x
imagPart :: Complex a -> a
imagPart (_ :+ y) = y
instance Distributive Complex where
distribute wc = fmap realPart wc :+ fmap imagPart wc
Given any functor-wrapped value, distribute
fmaps the getters of Complex
to it. distribute id
instantiates it as the function ((->) r
) functor. In this case, distribute id
is equal to realPart :+ imagPart
.
However, we cannot modify the elements this way because distribute
passes getters but not setters.
Here we introduce a new Logistic
class to provide settability to containers:
class Functor t => Logistic t where
deliver :: Contravariant f => f (t a -> t a) -> t (f (a -> a))
While the type of deliver
is slightly more intimidating, it's actually very close to the distribute
; the Functor
constraint is Contravariant
instead and the contents are endomorphisms.
Here's the instance for Complex
. deliver f
contramaps a setter function to f
for each field:
instance Logistic Complex where
deliver f
= contramap (\g (a :+ b) -> g a :+ b) f
:+ contramap (\g (a :+ b) -> a :+ g b) f
Instantiating the Op
contravariant functor, it is trivial to obtain a collection of setters.
newtype Op a b = Op { getOp :: b -> a }
setters :: Logistic t => t ((a -> a) -> t a -> t a)
setters = getOp <$> deliver (Op id)
ghci> let setR :+ setI = setters
ghci> setR (+1) (0 :+ 1)
1 :+ 1
ghci> setI (+1) (0 :+ 1)
0 :+ 2
deliver
has a generic default implementation which works for any single-constructor products.
This class can be useful to complement Distributive
. Generalisation to higher-kinded data would also be interesting.