数据类型的Show实例产生的错误

3

我一直在学习 Gabriel Gonzale 写的关于 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) 是一个完整的类型。 - user253751
此外,数据构造器Atomic只接受一个参数,而不是两个。 - user253751
2个回答

5
根据 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 m出现在Thread 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

您可以深入了解不可判定实例的领域,并表示如果可以显示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 的回答包含了如此出色的问题分解,我不能不将其标记为被接受的答案。只是想让你知道这个答案也有很大的价值,感谢你提供它。 - user4301448

4
首先,你收到的错误是一种“类型”错误。种类是类型的“类型”。就像类型分类值一样,种类分类类型。正如Haskell从值推断类型一样,它也从类型推断种类。我们使用相同的符号 :: 来表示一个值具有某种类型(例如1 :: Int )并表示一种类型具有某种种类(例如Int :: * )。
错误消息中提到了两种类型: * 是由值填充的类型的种类,例如 Int 和 Bool ,而 *-> *是类型构造函数的类型种类,例如 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"))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接