来自数据类型的Show实例的错误

时间:2017-04-21 04:35:56

标签: haskell

我一直在研究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的实例,但没有理解发生了什么。

2 个答案:

答案 0 :(得分: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

或者您可以深入研究undecidable instances的领域,并说如果可以显示Thread m r,则可以显示m (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

答案 1 :(得分:4)

首先,您收到的错误是错误。种类是一种类型的“类型”。就像类型对值进行分类一样,种类也会对类型进行分类就像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的定义在m构造函数的定义中将参数传递给Atomic,该构造函数的字段为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

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

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"))))