The composition law of Applicative in the Typeclassopedia

前提是你 提交于 2019-12-12 19:11:14

问题


I am reading Typeclassopedia and I was having trouble in the section on Applicatives. I think I (sort of) have it figured out but I want to see if my understanding is correct.

The laws for applicative made sense right up until the Composition law. I just couldn't parse the right-hand side of this:

u <*> (v <*> w) = pure (.) <*> u <*> v <*> w

So, I fired up GHCI and ran some experiments.

Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
Just 37

So this verifies the law, but I still didn't understand it. I tried some variations to see if I could get some insight:

Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just (+3) <*> Just 34

<interactive>:26:1: error:
    * Non type-variable argument in the constraint: Num (b -> b)
      (Use FlexibleContexts to permit this)
    * When checking the inferred type
        it :: forall b. (Num (b -> b), Num b) => Maybe b

So, composition works for two functions but not for three? I didn't understand why.

I then verified that <*> worked as I thought it would for a simple expression:

Prelude> Just (+1) <*> Just 1
Just 2

However, the following didn't work:

Prelude>  Just (+1) <*> Just (+2) <*> Just 34

<interactive>:15:2: error:
    * Non type-variable argument in the constraint: Num (b -> b)
      (Use FlexibleContexts to permit this)
    * When checking the inferred type
        it :: forall b. (Num (b -> b), Num b) => Maybe b

So, application isn't the same as composition. I had thought that Just (+2) <*> Just 34 would have resulted in Just 36 and Just (+1) <*> Just 36 would have resulted in Just 37. The composition operator, (.), is needed, but it only works for two functions. I just didn't understand why it was needed or how it worked.

And, just to throw more dirt into this water, I tried the following:

Prelude> Just (,) <*> Just 2

which failed because there was no instance of Show for the result. So I tried:

Prelude> :t Just (,) <*> Just 2
Just (,) <*> Just 2 :: Num a => Maybe (b -> (a, b))

This gave me a bit of insight. I needed to pass a second value to get a tuple returned. I tried:

Prelude> Just (,) <*> Just 2 Just 34

but that failed, and the error message really didn't help me figure out just where the error was. So, looking at the type of the above code I realized that it was the same as if I had entered Just (2, ) (or something like it, anyway). So, I tried:

Prelude> Just (,) <*> Just (2) <*> Just 34
Just (2,34)

I was actually surprised that this worked. But in that surprise came the germ of understanding (I think).

I went back to the definition of (<*>) in Typeclassopedia and discovered it is defined as left associative. I hadn't even considered associativity before this. So the above expression would actually evaluate something like this:

Prelude> Just (,) <*> Just (2) <*> Just 34
-- Apply the 2 as the first parameter of (,) leaving a function
-- that takes the second parameter
Just (2,) <*> Just 34
-- Now apply the 34 as the parameter to the resulting function
Just (2,34)

If this is correct then the example of the Composition law that worked evaluates like this:

Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
pure (+1 . ) <*> Just (+2) <*> Just 34
pure (+1 . +2) <*> Just 34
Just 37

This also explains why three functions cannot be composed this way:

Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just (+3) <*> Just 34
pure (+1 . ) <*> Just (+2) <*> Just (+3) <*> Just 34
pure (+1 . +2) <*> Just (+3) <*> Just 34

And, at this point we have a composed function expecting an instance of the Num typeclass but are, instead, passing it a function, which fails.

In a similar manner:

Prelude>  Just (+1) <*> Just (+2) <*> Just 34

fails because <*> isn't composition and is trying to apply a function, (+2) to another function, (+1), and running into a type constraint.

So, is my intuition correct here? Have I finally figured this out? Or is my (somewhat) educated guess still off base?


回答1:


This is just a case of not applying the substitution rule correctly, and forgetting the importance of parentheses in these substitutions (although some of them can be dropped later). The rule should be expressed as

u <*> (v <*> w) == (pure (.) <*> u <*> v) <*> w

Then

Just (+1) <*> (Just (+2) <*> Just 34)
    -- u = Just (+1)
    -- v = Just (+2)
    -- w = Just 34
 => (pure (.) <*> u <*> v) <*> w
 => (pure (.) <*> Just (+1) <*> Just (+2)) <*> Just 34

To add another layer on this, you have to use another pure (.):

Just (+3) <*> (Just (+1) <*> (Just (+2) <*> Just 34))
    -- u = Just (+3)
    -- v = Just (+1)
    -- w = Just (+2) <*> Just 34
 => (pure (.) <*> u <*> v) <*> w
 => pure (.) <*> Just (+3) <*> Just (+1) <*> (Just (+2) <*> Just 34)

If you wanted to work off of the pure form from the first stage, you'd have

Just (+3) <*> ((pure (.) <*> Just (+1) <*> Just (+2)) <*> Just 34)
    -- u = Just (+3)
    -- v = pure (.) <*> Just (+1) <*> Just (+2)
    -- w = Just 34
 => (pure (.) <*> u <*> v) <*> w
 => pure (.) <*> Just (+3) <*> (pure (.) <*> Just (+1) <*> Just (+2)) <*> Just 34

Which isn't nearly as pretty as one would hope, I'm afraid.




回答2:


As chi points out, your intuition is now correct. Quoting their comment:

Since (.) only composes two functions, it can't work on three.

This form of the composition law (i.e. in terms of (<*>)) is rather headache-inducing, specially if you want to work out further results using it (for a small example, cf. the latter part of bheklilr's answer). It gets a little easier to scan if we apply (pure f <*> x = f <$> x) to it:

u <*> (v <*> w) = ((.) <$> u <*> v) <*> w

Now the associativity in question is a little more obvious: applying v and then u to w through the functor is the same as composing u and v through the functor and then applying to w. For a major improvement, though, we have to switch to the so-called static arrow presentation:

idA :: Applicative f => f (a -> a)
idA = pure id

(.*) :: Applicative f => f (b -> c) -> f (a -> b) -> f (a -> c)
u .* v = (.) <$> u <*> v -- This looks familiar...

-- Conversely:
-- pure x = ($ x) <$> idA -- Alternatively: const x <$> idA
-- u <*> v = ($ ()) <$> (u .* (const <$> v))

In terms of (.*), the composition law becomes...

u .* (v .* w) = (u .* v) .* w

... which is transparently an associativity law.



来源:https://stackoverflow.com/questions/48796444/the-composition-law-of-applicative-in-the-typeclassopedia

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!