我正在阅读Typeclassopedia,我在“申请人”一节中遇到了麻烦。我想我(有点)想出来但我想知道我的理解是否正确。
适用的法律在构成法之前有意义。我无法解析这个问题的右侧:
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中(<*>)
的定义,发现它被定义为左关联。在此之前我甚至没有考虑过相关性。所以上面的表达式实际上会评估这样的东西:
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)
,并且运行到类型约束中。
那么,我的直觉在这里是否正确?我终于搞清楚了吗?或者我的(有点)受过教育的猜测仍然偏离基础?
答案 0 :(得分:4)
这只是一个没有正确应用替换规则的情况,并且忘记了这些替换中括号的重要性(尽管其中一些可以在以后删除)。该规则应表示为
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
我恐怕不会像人们希望的那样漂亮。
答案 1 :(得分:4)
正如志所指出的那样,你的直觉现在是正确的。引用their comment:
由于
(.)
只编写两个函数,因此它不能处理三个函数。
这种形式的组成法(即(<*>)
)相当令人头疼,特别是如果你想用它来计算更多的结果(对于一个小例子,参见{{}}的后半部分{3}})。如果我们应用(pure f <*> x = f <$> x
),它会更容易扫描:
u <*> (v <*> w) = ((.) <$> u <*> v) <*> w
现在相关的关联性更加明显:通过仿函数将v
和u
应用于w
与撰写u
和{{1}相同通过仿函数然后应用于v
。但是,要获得重大改进,我们必须切换到所谓的静态箭头演示文稿:
w
就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))
而言,构成法变为......
(.*)
......透明地说是一种相关性法则。