【问题标题】:Is this Haskell type inference in action, or something else?这是 Haskell 类型的推理,还是其他?
【发布时间】:2011-11-12 14:55:40
【问题描述】:

我正在阅读在线LYAH 书籍(该链接会将您直接带到我的问题所涉及的部分)。

作者定义了一个二叉树数据类型,并展示了如何通过实现 foldMap 函数使其成为 Foldable 类型的实例(在 Data.Foldable 中定义):

import Data.Monoid
import qualified Data.Foldable as F

data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

instance F.Foldable Tree where  
  foldMap f Empty = mempty  
  foldMap f (Node x l r) = F.foldMap f l `mappend`  
                           f x           `mappend`  
                           F.foldMap f r 

foldMap的类型声明如下:

F.foldMap :: (Monoid m, F.Foldable t) => (a -> m) -> t a -> m

所以它接受一个函数,该函数接受一个类型为“a”的实例并返回一个幺半群。

现在作为一个例子,作者创建一个Tree实例

    testTree = Node 5  
                 (Node 3  
                    (Node 1 Empty Empty)  
                    (Node 6 Empty Empty)  
                 )  
                 (Node 9  
                    (Node 8 Empty Empty)  
                    (Node 10 Empty Empty)  
                 )  

并执行以下折叠(为可折叠类型定义):

F.foldl (+) 0 testTree -- the answer is 42 (sum of the Node Integers)

我的问题是,Haskell 如何计算出 Integer 类型的加法 - 查询 Haskell 的 testTree 类型给出 Tree [Integer] - 可以被视为一个幺半群运算(如果我的术语是正确的)?

(我自己的答案尝试:作者在本节前几段描述了 Num 类型如何以两种不同的方式解释为 Monoid 类型;通过将它们包装到 SumProduct 类型中,其中 (+) 和 (*) 作为 mappend 函数,0 和 1 作为 mempty 元素。是否 (Tree a) 中的“a”类型以某种方式被推断为属于​​ Sum 类型(Haskell 的不同方式根据上下文解释数值)还是完全不同?]

【问题讨论】:

    标签: haskell type-inference


    【解决方案1】:

    幺半群实际上并没有在这里使用。最后一行是使用F.foldl,其签名为F.Foldable t => (a -> b -> a) -> a -> t b -> a。基本上,您通过提供 (+) 和 0 来“手动”使用幺半群。

    如果你想“隐式”使用一个幺半群,你可以使用F.fold(它的签名是(F.Foldable t, Monoid m) -> t m -> m)。在这种情况下,如果你尝试一下,你会得到:

    *Main> F.fold testTree
    
    <interactive>:1:1:
        No instance for (Monoid Integer)
          arising from a use of `F.fold'
        Possible fix: add an instance declaration for (Monoid Integer)
        In the expression: F.fold testTree
        In an equation for `it': it = F.fold testTree
    *Main> :t F.foldl
    

    现在,GHCI 抱怨 Integer 没有 Monoid 实例,这是应该的。您必须通过包装整数来选择 Sum 或 Product。为此我们可以使用F.foldMap(签名(F.Foldable t, Monoid m) =&gt; (a -&gt; m) -&gt; t a -&gt; m):

    *Main> F.foldMap Sum testTree
    Sum {getSum = 42}
    

    【讨论】:

    • 感谢您提到 F.fold,它的行为符合我(错误地)期望 foldl 的行为......
    【解决方案2】:

    我的问题是,Haskell 如何确定 Integer 类型的加法 - 向 Haskell 查询 testTree 的类型给出 Tree [Integer] - 可以被视为一个幺半群运算(如果我的术语是正确的)?

    不可能!其实Integer没有Monoid的实例。

    现在,不要误会我的意思——整数加法下的幺半群。然而,它们也是乘法下的幺半群,而且 Haskell 无法知道使用哪个,因此使用了 newtype 包装器。

    但是……这一切都不是这里发生的事情。继续……

    (我自己的答案尝试:作者在本节之前的几段描述了如何以两种不同的方式将 Num 类型解释为 Monoid 类型;通过使用 (+) 将它们包装成 Sum 和 Product 类型和(*) 作为 mappend 函数,0 和 1 分别作为 mempty 元素。(Tree a) 中的“a”的类型是否以某种方式被推断为属于​​ Sum 类型(Haskell 根据不同解释数值的方式上下文)还是完全不同的东西?]

    不错的猜测,但这种推断(根据您提供的参数使用 Sum 查找实例)超出了 Haskell 可以为您做的事情。

    这里有两个关键点——首先,Monoid 约束仅用于某些功能,一般不用于折叠。特别是,foldl 实际上根本不需要Monoid 实例,因为您提供了二进制操作和初始值供它使用。

    第二点是我怀疑你真正想要的——当你定义的只是foldMap 时,它如何创建一个不需要Monoid 的通用foldl,它是如何创建的?要回答这个问题,我们可以简单地 look at the default implementation of foldl

    foldl :: (a -> b -> a) -> a -> t b -> a
    foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
    

    这里,Endo is another newtype wrapper,专门用于函数a -&gt; a,给出组合的Monoid,以id 作为标识,而Dual is a wrapper 反转Monoid 的方向。所以这里实际使用的Monoid 可以将(+) 的使用与函数组合粘合在一起,然后将结果应用于种子值。

    【讨论】:

    • 很好的解释。 appEndo 听起来像是哈利波特的咒语。
    • @Dan Burton:确实。你也不是唯一一个remark on that 的人。
    • 你知道,我的大脑可能正是因为那个 HWN 将 appEndo 与 Potter 联系在一起,而我忘记了我读过它。我当时不明白;现在我对appEndo 的实际作用有了更好的了解:)
    • @Dan Burton:如果a -&gt; a 是一个没有包装器的Monoid 实例,那就太好了,但是唉,这会与逐点实例Monoid b =&gt; a -&gt; b 冲突。但是,如果您想进一步思考,请考虑一下:a -&gt; a 给出了简单迭代循环的幺半群,可以将其提升到类型级别,作为具有* -&gt; * 类型的嵌套参数数据结构的幺半群。您如何限制这一点,以使任何迭代序列都给出相同的结果类型,同时保留幺半群结构,以及该类型的折叠是什么样的?
    • 感谢您的解释...我确实认为我的“答案”(Haskel 将 Integer 类型升级为 Sum)不太可能,但我没有想到检查 foldl 的实现。尽管我很喜欢 LYAH,但我认为作者在相关部分的阐述可能需要一点阐述
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多