Typeclassopedia中的应用组成法则

时间:2018-02-14 21:22:14

标签: haskell applicative

我正在阅读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 36Just (+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),并且运行到类型约束中。

那么,我的直觉在这里是否正确?我终于搞清楚了吗?或者我的(有点)受过教育的猜测仍然偏离基础?

2 个答案:

答案 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

现在相关的关联性更加明显:通过仿函数将vu应用于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)) 而言,构成法变为......

(.*)

......透明地说是一种相关性法则。