Typeclassopedia中Applicative的构成规律
The composition law of Applicative in the Typeclassopedia
我正在阅读 Typeclassopedia,但我在 Applicatives 部分遇到了问题。我想我(有点)明白了,但我想看看我的理解是否正确。
在组合法出现之前,应用法则都是有意义的。我只是无法解析它的右侧:
u <*> (v <*> w) = pure (.) <*> u <*> v <*> w
因此,我启动了 GHCI 并 运行 进行了一些实验。
Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
Just 37
所以这验证了规律,但我还是没看懂。我尝试了一些变体,看看是否能获得一些见解:
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
那么,组合适用于两个功能而不适用于三个功能?我不明白为什么。
然后我验证了 <*>
是否像我想的那样对一个简单的表达式有效:
Prelude> Just (+1) <*> Just 1
Just 2
但是,以下方法不起作用:
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
因此,应用与组合不同。我原以为 Just (+2) <*> Just 34
会导致 Just 36
而 Just (+1) <*> Just 36
会导致 Just 37
。组合运算符 (.)
是必需的,但它仅适用于两个函数。我只是不明白为什么需要它或它是如何工作的。
而且,为了向水中投入更多的污垢,我尝试了以下方法:
Prelude> Just (,) <*> Just 2
失败,因为结果没有 Show
的实例。所以我尝试了:
Prelude> :t Just (,) <*> Just 2
Just (,) <*> Just 2 :: Num a => Maybe (b -> (a, b))
这让我有点见识。我需要传递第二个值才能返回 tuple
。我试过了:
Prelude> Just (,) <*> Just 2 Just 34
但是失败了,错误消息确实没有帮助我找出错误所在。因此,查看上面代码的类型后,我意识到它与输入 Just (2, )
(或类似的东西)一样。所以,我尝试了:
Prelude> Just (,) <*> Just (2) <*> Just 34
Just (2,34)
我真的很惊讶这能奏效。但在那惊喜中萌生了理解的萌芽(我认为)。
我回到Typeclassopedia中(<*>)
的定义,发现它被定义为left associative。在此之前我什至没有考虑过关联性。所以上面的表达式实际上会这样计算:
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)
如果这是正确的,那么有效的组合法示例的评估如下:
Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
pure (+1 . ) <*> Just (+2) <*> Just 34
pure (+1 . +2) <*> Just 34
Just 37
这也解释了为什么三个函数不能这样组合:
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
而且,在这一点上,我们有一个组合函数,它需要一个 Num
类型类的实例,但相反,它传递给它一个函数,但失败了。
以类似的方式:
Prelude> Just (+1) <*> Just (+2) <*> Just 34
失败,因为 <*>
不是组合,并且正在尝试将一个函数 (+2)
应用于另一个函数, (+1)
并将 运行 应用于类型约束。
所以,我的直觉在这里是正确的吗?我终于明白了吗?或者我的(有点)受过教育的猜测仍然是错误的?
这只是一个没有正确应用替换规则的情况,忘记了括号在这些替换中的重要性(尽管其中一些可以稍后删除)。规则应表示为
u <*> (v <*> w) == (pure (.) <*> u <*> v) <*> w
然后
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
要在此添加另一层,您必须使用另一个 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)
如果您想从第一阶段开始使用 pure
表格,您需要
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
恐怕没有人们希望的那么漂亮。
正如 chi 指出的那样,您的直觉现在是正确的。引用 :
Since (.)
only composes two functions, it can't work on three.
这种形式的合成法则(即 (<*>)
的形式)相当令人头疼,特别是如果你想使用它来计算出更多的结果(举个小例子,参见后面的部分) )。如果我们对其应用 (pure f <*> x = f <$> x
),它会变得更容易扫描:
u <*> (v <*> w) = ((.) <$> u <*> v) <*> w
现在所讨论的关联性更加明显:通过函子应用 v
然后 u
到 w
与组合 u
和 v
通过函子,然后应用到 w
。但是,为了进行重大改进,我们必须切换到所谓的 static arrow 表示:
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))
根据(.*)
,合成法则变为...
u .* (v .* w) = (u .* v) .* w
...这显然是一个结合律。
我正在阅读 Typeclassopedia,但我在 Applicatives 部分遇到了问题。我想我(有点)明白了,但我想看看我的理解是否正确。
在组合法出现之前,应用法则都是有意义的。我只是无法解析它的右侧:
u <*> (v <*> w) = pure (.) <*> u <*> v <*> w
因此,我启动了 GHCI 并 运行 进行了一些实验。
Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
Just 37
所以这验证了规律,但我还是没看懂。我尝试了一些变体,看看是否能获得一些见解:
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
那么,组合适用于两个功能而不适用于三个功能?我不明白为什么。
然后我验证了 <*>
是否像我想的那样对一个简单的表达式有效:
Prelude> Just (+1) <*> Just 1
Just 2
但是,以下方法不起作用:
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
因此,应用与组合不同。我原以为 Just (+2) <*> Just 34
会导致 Just 36
而 Just (+1) <*> Just 36
会导致 Just 37
。组合运算符 (.)
是必需的,但它仅适用于两个函数。我只是不明白为什么需要它或它是如何工作的。
而且,为了向水中投入更多的污垢,我尝试了以下方法:
Prelude> Just (,) <*> Just 2
失败,因为结果没有 Show
的实例。所以我尝试了:
Prelude> :t Just (,) <*> Just 2
Just (,) <*> Just 2 :: Num a => Maybe (b -> (a, b))
这让我有点见识。我需要传递第二个值才能返回 tuple
。我试过了:
Prelude> Just (,) <*> Just 2 Just 34
但是失败了,错误消息确实没有帮助我找出错误所在。因此,查看上面代码的类型后,我意识到它与输入 Just (2, )
(或类似的东西)一样。所以,我尝试了:
Prelude> Just (,) <*> Just (2) <*> Just 34
Just (2,34)
我真的很惊讶这能奏效。但在那惊喜中萌生了理解的萌芽(我认为)。
我回到Typeclassopedia中(<*>)
的定义,发现它被定义为left associative。在此之前我什至没有考虑过关联性。所以上面的表达式实际上会这样计算:
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)
如果这是正确的,那么有效的组合法示例的评估如下:
Prelude> pure (.) <*> Just (+1) <*> Just (+2) <*> Just 34
pure (+1 . ) <*> Just (+2) <*> Just 34
pure (+1 . +2) <*> Just 34
Just 37
这也解释了为什么三个函数不能这样组合:
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
而且,在这一点上,我们有一个组合函数,它需要一个 Num
类型类的实例,但相反,它传递给它一个函数,但失败了。
以类似的方式:
Prelude> Just (+1) <*> Just (+2) <*> Just 34
失败,因为 <*>
不是组合,并且正在尝试将一个函数 (+2)
应用于另一个函数, (+1)
并将 运行 应用于类型约束。
所以,我的直觉在这里是正确的吗?我终于明白了吗?或者我的(有点)受过教育的猜测仍然是错误的?
这只是一个没有正确应用替换规则的情况,忘记了括号在这些替换中的重要性(尽管其中一些可以稍后删除)。规则应表示为
u <*> (v <*> w) == (pure (.) <*> u <*> v) <*> w
然后
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
要在此添加另一层,您必须使用另一个 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)
如果您想从第一阶段开始使用 pure
表格,您需要
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
恐怕没有人们希望的那么漂亮。
正如 chi 指出的那样,您的直觉现在是正确的。引用
Since
(.)
only composes two functions, it can't work on three.
这种形式的合成法则(即 (<*>)
的形式)相当令人头疼,特别是如果你想使用它来计算出更多的结果(举个小例子,参见后面的部分) pure f <*> x = f <$> x
),它会变得更容易扫描:
u <*> (v <*> w) = ((.) <$> u <*> v) <*> w
现在所讨论的关联性更加明显:通过函子应用 v
然后 u
到 w
与组合 u
和 v
通过函子,然后应用到 w
。但是,为了进行重大改进,我们必须切换到所谓的 static arrow 表示:
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))
根据(.*)
,合成法则变为...
u .* (v .* w) = (u .* v) .* w
...这显然是一个结合律。