这个Haskell类型的推理是在行动还是其他什么?

时间:2011-09-08 01:31:47

标签: haskell type-inference

我正在阅读在线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”的实例并返回一个monoid。

现在作为一个例子,作者创建了一个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] - 可以被视为一个monoid操作(如果我的术语是正确的)?

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

2 个答案:

答案 0 :(得分:12)

  

我的问题是,Haskell如何计算出Integer类型的加法 - 查询Haskell的testTree类型给出Tree [Integer] - 可以看作是一个monoid操作(如果我的术语是正确的)?

它不能!实际上,Monoid没有Integer个实例。

现在,不要误解我的意思 - 整数 是一个绰号。然而,它们在乘法中也是一个幺半群,并且Haskell无法知道使用哪个,因此newtype包装器。

但......这些都不是这里发生的事情。继续......

  

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

不是一个糟糕的猜测,但这种推断(根据你给出的参数使用Sum查找实例)超出了Haskell可以为你做的事情。

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

第二点是我怀疑你真正追求的 - 当你所定义的是foldl时,它如何创建一个不需要Monoid的通用foldMap ,哪个呢?要回答这个问题,我们只需foldl look at the default implementation {/ 3>}

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 -> a合成的Monoid函数,id作为标识,而Dual is a wrapper则反转方向一个Monoid。所以它实际上使用的Monoid是这样它可以将(+)与函数组合一起使用,然后将结果应用于种子值。

答案 1 :(得分:5)

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

如果您想“隐式”使用monoid,可以使用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) => (a -> m) -> t a -> m):

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