Он не только сохраняет ассоциативность, я бы сказал, что, возможно, основная идея за аппликативным l aws в первую очередь!
Вспомните математическую форму выражения class:
class Functor f => Monoidal f where
funit :: () -> f ()
fzip :: (f a, f b) -> f (a,b)
с l aws
zAssc: fzip (fzip (x,y), z) ≅ fzip (x, fzip (y,z)) -- modulo tuple re-bracketing
fComm: fzip (fmap fx x, fmap fy y) ≡ fmap (fx<a href="https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Arrow.html#v:-42--42--42-" rel="nofollow noreferrer" title="(***) :: (a->α) -> (b->β) -> (a,b)->(α,β)">***</a>fy) (fzip (x,y))
fIdnt: fmap id ≡ id -- ─╮
fCmpo: fmap f . fmap g ≡ fmap (f . g) -- ─┴ functor laws
В этом подходе liftA2
учитывается при преобразовании функции с кортежным значением в уже готовую заархивированную пару:
liftZ2 :: ((a,b)->c) -> (f a,f b) -> f c
liftZ2 f = fmap f . fzip
ie
liftZ2 f (a,b) = f <$> fzip (a,b)
Теперь предположим, что мы дали
g :: (G,G) -> G
gAssc: g (g (α,β), γ) ≡ g (α, g (β,γ))
или без точек (снова игнорируя замену скобок кортежа)
gAssc: g . (g***id) ≅ g . (id***g)
Если мы напишем все в этом стиле, легко увидеть, что сохранение ассоциативности в основном составляет zAssc
, причем все, что касается g
, происходит на отдельном шаге fmap
:
liftZ2 g (liftZ2 g (a,b), c)
{-liftA2'-} ≡ g <$> fzip (g <$> fzip (a,b), c)
{-fIdnt,fComm-} ≡ <b>g . (g***id)</b> <$> <b>fzip (fzip (a,b), c)
{-gAssc,zAssc-} ≡ g . (id***g)</b> <$> <b>fzip (a, fzip (b,c))</b>
{-fComm,fIdnt-} ≡ g <$> fzip (a, g <$> fzip (b,c))
{-liftA2'-} ≡ liftZ2 g (a, liftZ2 g (b,c))