0

Applicative is declared as

class   Functor f   =>  Applicative f   where
pure    ::  a   ->  f   a
(<*>)   ::  f   (a  ->  b)  ->  f   a   ->  f   b

One of applicative's laws is:

x <*> y <*> z = ( pure (.) <*> x <*> y) <*> z

where (.) is composition between functions:

(.) ::  (b  ->  c)  ->  (a  ->  b)  ->  (a  ->  c)
f   .   g   =   \x  ->  f   (g  x)

On the right hand side of the law,

  • does pure (.) have type f((b -> c) -> (a -> b) -> (a -> c))?
  • does x have type f(b->c)?
  • does y have type f(a->b)?
  • does z have type f(a)?

On the left hand side of the law,

  • does x have type f(a->b->c)?
  • does y have type f(a)?
  • does z have type f(b)?

Thanks.

Tim
  • 1
  • 141
  • 372
  • 590

2 Answers2

6

The applicative laws are easier understood in the equivalent monoidal functor representation:

class Functor f => Monoidal f where
  pureUnit :: f ()
  fzip :: f a -> f b -> f (a,b)
--pure x = fmap (const x) pureUnit
--fs<*>xs = fmap (\(f,x)->f x) $ fzip fs xs
--pureUnit = pure ()
--fzip l r = (,) <$> l <*> r

Then, the law you're asking about is this:

fzip x (fzip y z) ≅ fzip (fzip x y) z

where by p ≅ q I mean, equivalent up to re-association of the tuple type, i.e. explicitly

fzip x (fzip y z) ≡ fmap (\((a,b),c)->(a,(b,c))) $ fzip (fzip x y) z

So this is really just an associative law. Even better visible when written with an infix (⋎) = fzip:

x ⋎ (y ⋎ z) ≅ (x ⋎ y) ⋎ z
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
2

Stating the law again for ease of reference (which I have corrected by putting the parenthese in, `(<*>) is left associative so the ones on the LHS of the equality are necessary):

x <*> (y <*> z) = ( pure (.) <*> x <*> y) <*> z

let's start with your first question:

On the right hand side of the law,

does pure (.) have type f((b   ->  c)  ->  (a  ->  b)  ->  (a  ->  c))?

Yes it does. (.) has type (b -> c) -> (a -> b) -> (a -> c), so pure (.) must have type "f of that".

We can use this to determine the types of the other identifiers here. In the expression m <*> n, m and n have the general types f (a -> b) and f a. So if we take m as (.), which has the type shown above, we see that n - which corresponds to x on the RHS of the equality - must have type f (b -> c). And then pure (.) <*> x will have type f ((a -> b) -> (a -> c)), which by the same reasoning means y will have type f (a -> b). This produces a type of f (a -> c) for ( pure (.) <*> x <*> y) and of f a for z - and thus an overall result type, for the whole RHS (and therefore also the whole LHS) of f c.

So to summarise, just from analysing the RHS, we see that:

  • x :: f (b -> c)
  • y :: f (a -> b)
  • z :: f a

It's now easy to check that these work out on the LHS too - y <*> z will have type f b, so x <*> (y <*> z) will have type f c.

I note that there's nothing clever involved in the above, it's just simple type algebra, which can be carried out without any advanced understanding.

Robin Zigmond
  • 17,805
  • 2
  • 23
  • 34