问题
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