【问题标题】:Why type constructors can not be used in typeclass definition in haskell?为什么在haskell的类型类定义中不能使用类型构造函数?
【发布时间】:2021-02-02 17:23:15
【问题描述】:
data LinkedList a = Empty | Cons a (LinkedList a)   deriving (Eq, Show)

instance  Foldable LinkedList where
  foldMap _ Empty  = mempty
  foldMap f (a `Cons` ll) =  LinkedList (f a)  (foldMap f ll)

似乎 LinkedList 构造函数不在范围内?为什么?

编译错误:

        Data constructor not in scope: LinkedList :: m -> m -> m
   |        
17 |   foldMap f (a `Cons` ll) =  LinkedList (f a)  (foldMap f ll)
   |                              ^^^^^^^^^^
            
            

我搜索了一个解决方案,似乎我应该使用“映射”而不是构造函数。这非常令人困惑。我没有在任何地方定义任何 Monoid 类型类。

你能解释为什么mappened 而不是构造函数吗?以及Monoid 函数mappenedmempty 是在哪里定义的?

编辑 ---------------------------------------------- ----------------------

当我从 mappened 改回来时,我犯了一个错误。抱歉,正确的问题是:

instance  Foldable LinkedList where
  foldMap _ Empty  = mempty
  foldMap f (a `Cons` ll) =  Cons (f a)  (foldMap f ll)

编译器错误:

     • Occurs check: cannot construct the infinite type:
        m ~ LinkedList m
    • In the second argument of ‘Cons’, namely ‘(foldMap f ll)’
      
   |        
17 |   foldMap f (a `Cons` ll) =  Cons (f a)  (foldMap f ll)
   |            

                           ^^^^^^^^^^^^

为什么我不能递归地使用 foldMap?以及 Monoid 函数的定义位置。

【问题讨论】:

  • 你没有LinkedList数据构造函数,只是一个同名类型。类型和值(包括数据构造函数)存在于完全不同的命名空间中,即使两者都是大写的。
  • 你需要注意你的拼写。你可以(有时)在这里逃脱;您正在编程的计算机将不那么乐于助人。
  • 至于为什么需要mappend,那是因为对foldMap 的递归调用会返回一个任意Monoid 中的值——所以mappend(连同mempty)是唯一的东西你“知道”你有。
  • @RobinZigmond 抱歉,我更正了这个问题。

标签: haskell constructor typeclass monoids


【解决方案1】:

data LinkedList a = Empty | Cons a (LinkedList a)

你有

  • LinkedList,它是一个 type 构造函数:它将一般 type a 作为参数,并返回类型 LinkedList a
  • Cons,它是一个 value 构造函数:它接受一个普通类型 avalue 和一个 @987654327 类型的 value 作为参数@。

另一方面,这里

  foldMap f (a `Cons` ll) =  LinkedList (f a)  (foldMap f ll)

你使用 LinkedList 好像是一个 value 构造函数,这是错误的。

【讨论】:

  • 对不起,我弄错了。从 mappend 返回时。
【解决方案2】:

编译器错误消息应该包含足够的详细信息,以便您找出代码有什么问题:

     • Occurs check: cannot construct the infinite type:
        m ~ LinkedList m
    • In the second argument of ‘Cons’, namely ‘(foldMap f ll)’
      
   |        
17 |   foldMap f (a `Cons` ll) =  Cons (f a)  (foldMap f ll)
   |            

                           ^^^^^^^^^^^^

忽略我接受的关于“发生检查”和“无限类型”的部分,这似乎有点令人困惑,它抱怨foldMap f ll 应该具有类型m,而实际类型为LinkedList m,或者可能(事实证明)反过来。所以让我们自己看一下类型。

它以foldMap的类型开头,我们可以在documentation中轻松找到:

foldMap :: Monoid m => (a -> m) -> t a -> m

这意味着foldMap f ll 将具有m 类型,对于任何Monoid m(调用者选择作为函数f 的返回类型的特定Monoid)。

同时,f 具有 a -> m 类型,因此 f a 必须具有 m 类型 - 再次用于 f 选择的任意 Monoid 实例。所以当f a作为Cons的第一个参数时——Cons (f a) (foldMap f ll)——我们可以对比Cons的定义,它来自于类型定义:

data LinkedList a = Empty | Cons a (LinkedList a)

这告诉我们,由于f a 具有m 类型,Cons (f a) (foldMap f ll) 必须假定为LinkedList m 类型的值。因此foldMap f ll 必须具有相同的类型。 (因为定义中的两种LinkedList 类型都应用于同一类型a。)

这给了我们编译器报告的问题 - foldMap f ll 必须同时属于 m 类型和 LinkedList m 类型。

(旁白:GHC 在那一点上实际上并没有放弃,它假设类型实际上是彼此相等的,并查看导致的结果。但在这里它只会导致无限回归:相关的值必须具有类型m 等于 LinkedList m 等于 LinkedList (LinkedList m) 等等。Haskell 在值定义中可以处理像这样的无限回归,但不允许它用于类型 - 因此“不能构造无限类型”。)

我们如何修复此错误并获得正确的foldMap 定义?您尝试将f afoldMap f ll 结合起来计算foldMap f (Cons a ll) 绝对是正确的想法。这两个都是m 类型,用于f 所针对的特定(任意)Monoid。我们不一定能将它们与“普通函数”结合起来,比如(+),因为我们不知道m 是什么类型——它不一定是数字类型或其他类型。但是我们知道的一件事是它是Monoid 的一个实例——因此我们确实可以将任意两个这样的值与Monoid 方法mappend 结合起来。事实上,我们真的无能为力,因为我们对这里的m 类型一无所知除了它是Monoid 的一个实例 - 因此有mappend :: m -> m -> m(和mempty :: m)可用。

所以只有一种方法可以将这些值组合成正确的定义,即:

foldMap f (Cons a ll) = mappend (f a) (foldMap f ll)

右侧看不到Cons(或Empty) - 如果这让您感到困惑,请考虑foldMapLinkedList 作为输入(在此特定case 是 Cons a ll 参数),但它不输出 LinkedList,而是在任意选择的 Monoid m 中输出一个值。

我希望这会有所帮助,但如果您以前没有遇到过Monoids,可能会有点难以理解——尤其是如果您在没有首先了解 Monoids 的情况下试图理解 Foldable。我强烈建议您进一步阅读这些主题,包括出色的 Typeclassopedia 作为起点(在这种情况下,主要是关于 Monoid 和 Foldable 的部分)。

【讨论】:

  • 非常感谢。非常完整和有见地。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多