【问题标题】:Errors from data type's Show instance来自数据类型的 Show 实例的错误
【发布时间】:2017-04-21 04:35:56
【问题描述】:

我一直在研究 Gabriel Gonzale 的出色 blog post on Free Monads。为了帮助我理解,我想为以下类型创建一个 Show 实例并在 GHCi 中使用它,类型(来自博客文章)是:

data Thread m r = Atomic (m (Thread m r)) | Return r

我的 Show 实例是:

instance (Show m, Show r) => Show (Thread m r) where
  show (Atomic m x) = "Atomic " ++ show m ++ " " ++ show x
  show (Return x)   = "Return " ++ show x

不幸的是,GHCi 在尝试加载文件时给了我这个错误:

• Expected kind ‘* -> *’, but ‘m’ has kind ‘*’
• In the first argument of ‘Thread’, namely ‘m’
  In the first argument of ‘Show’, namely ‘Thread m r’
  In the instance declaration for ‘Show (Thread m r)’

所以,最重要的是:这个错误是什么意思,我为什么会得到它?我认为回答这个问题将极大地帮助我理解这篇博文(尽管方式有点迂回)。 另外,一个有效的 Show 实现会是什么样子?我尝试查看 Either 的实例,但不明白发生了什么。

【问题讨论】:

  • m 是一个类型构造函数,如IOMaybe。它不是一个完整的类型(如IO ()Maybe String)。但是m (Thread m r) 是一个完整的类型。
  • 另外,数据构造函数Atomic只接受一个参数,而不是两个。

标签: haskell


【解决方案1】:

根据Thread m r = Action (m (Thread m r) | …m 是接受一种类型并返回另一种类型的东西。我们称m 为类型构造函数或类型为* -> *。另一个类似的例子是Maybe:

data Maybe a = Just a | Nothing

Maybe 本身不是一种类型。您必须为其提供另一种类型,例如Maybe IntMaybe String

现在Show 需要一个类型为* 的类型。但是Thread 需要* -> *。因此,GHC 放弃了。由于Show mThread m r 之前出现,GHC 认为它的类型是*,它不适用于Action (m (Thread m r)),因为这需要一个类型构造函数(* -> *)。

顺便说一下,这个问题就是为什么某些包中存在像Show1 这样的类。您可以采用它并然后编写您的 Show 实例:

instance (Show1 m, Show r) => Show (Thread m r) where
  show (Atomic x) = "Atomic " ++ show1 x
  show (Return x) = "Return " ++ show x

或者你可以深入undecidable instances的领域,说如果m (Thread m r)可以显示,Thread m r可以显示:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

instance (Show r, Show (m (Thread m r))) => Show (Thread m r) where
  show (Atomic x) = "Atomic " ++ show x
  show (Return x) = "Return " ++ show x

【讨论】:

  • 这是一个很好的答案,我什至在查看 Show 的 Either 实例,想知道 Show1 类是否具有相关性。这绝对值得成为公认的答案,但乔恩·珀迪 (Jon Purdy) 的问题非常详细,我无法不将他的答案标记为已接受。只是想让您知道此答案也很有价值,并感谢您提供它。
【解决方案2】:

首先,您收到的错误是 kind 错误。种类是类型的“类型”。正如类型对值进行分类一样,种类对类型进行分类。就像 Haskell 从值推断类型一样,它也从类型推断类型。我们使用相同的符号:: 来表示一个值具有某种类型(例如,1 :: Int)并表示一个类型具有某种类型(例如,Int :: *)。

错误信息中提到了两种:*是被值所占据的类型,例如IntBool,而* -> *是类型构造函数 例如Maybe[]IO。您可以将类型构造函数视为类型级函数:Maybe 将类型(*)作为参数并返回一个类型(也是*)作为结果,例如Maybe Int :: *。种类的柯里化方式与函数相同;例如,Either 具有 * -> * -> * 类型,因为它需要两个 * 类型的参数来生成类型 * 的类型:

Either :: * -> * -> *
Either Int :: * -> *
Either Int Bool :: *

所以错误来自Show 实例的类型类约束:

instance (Show m, Show r) => Show (Thread m r) where
          ------

Show 是可以显示的* 类型的类。您可以通过在 GHCi 中输入:kind Show(或:k Show)来查看:

> :kind Show
Show :: * -> Constraint

所以Show 采用一种* 的类型并返回一个类型类约束。在不涉及约束的太多细节的情况下,这意味着Show m 意味着m :: *。但是,Thread 的定义在其Atomic 构造函数的定义中将参数传递给m,该构造函数具有m (Thread m r) 类型的字段。看看Thread的那种:

> :kind Thread
Thread :: (* -> *) -> * -> *

这意味着m :: * -> *,因此不匹配。

下一个错误是在你的Show实例的实现中,即:

show (Atomic m x) = "Atomic " ++ show m ++ " " ++ show x
               -                        ----------------

在这里,您提供了一个匹配多个字段的模式,但 Atomic 只有一个字段。您应该将实现更改为以下内容:

show (Atomic m) = "Atomic " ++ show m

如果您删除 Show m 约束,您将看到更有用的错误消息:

Could not deduce (Show (m (Thread m r)))
  arising from a use of ‘show’
from the context (Show r)
  bound by the instance declaration at …
In the second argument of ‘(++)’, namely ‘show m’
In the expression: "Atomic " ++ show m
In an equation for ‘show’: show (Atomic m) = "Atomic " ++ show m

这表示您尝试在 m (Thread m r) 类型的值上调用 show,但您在上下文中没有该约束。所以你可以添加它:

instance (Show (m (Thread m r)), Show r) => Show (Thread m r) where
          ---------------------

这不是“标准”Haskell,因此 GHC 开始建议允许它的扩展:

Non type-variable argument in the constraint: Show (m a)
(Use FlexibleContexts to permit this)
In the context: (Show (m a), Show r)
While checking an instance declaration
In the instance declaration for ‘Show (Thread m r)’

让我们尝试添加-XFlexibleContexts(在命令行中使用ghci … -XFlexibleContexts,在会话中使用:set -XFlexibleContexts,或者在源文件中使用{-# LANGUAGE FlexibleContexts #-}),因为它实际上是一个非常良性的扩展。现在我们得到一个不同的错误:

Variable ‘a’ occurs more often than in the instance head
  in the constraint: Show (m a)
(Use UndecidableInstances to permit this)
In the instance declaration for ‘Show (Thread m r)’

我们可以添加-XUndecidableInstances——这意味着您正在编写 GHC 无法证明会停止的类型级计算。有时这是不可取的,但在这种情况下很好,因为我们知道实例解析要么找到可接受的Show 实例,要么失败。现在编译器接受了它,我们可以尝试我们的Show 实例,比如m ~ []r ~ Int 这样简单的例子:

> Atomic [Atomic [Return 1, Return 2]] :: Thread [] Int
Atomic [Atomic [Return 1,Return 2]]

但是,请注意,当您将 m 设置为没有任何 Show 实例的类型构造函数时,这将不起作用,例如 IO

> Atomic (return (Atomic (return (Return 1) >> return (Return 2)))) :: Thread IO Int

No instance for (Show (IO (Thread IO Int)))
  arising from a use of ‘print’
In a stmt of an interactive GHCi command: print it

此外,您可能还注意到您的 Show 实例的结果中缺少一些括号:

> Atomic (Right (Atomic (Left "asdf"))) :: Thread (Either String) Int
Atomic Right Atomic Left "asdf"

这是一个简单的解决方法,我将留给你。

这应该使您的实例能够使用文章中的Toy 等数据类型,通过Free

> Atomic (Free (Output "foo" (Pure (Return "bar"))))
Atomic (Free (Output "foo" (Pure Return ("bar"))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-18
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    相关资源
    最近更新 更多