我一直在研究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的实例,但没有理解发生了什么。
答案 0 :(得分:5)
根据Thread m r = Action (m (Thread m r) | …
,m
是一种类型并返回另一种类型的东西。我们将m
称为类型构造函数或类型* -> *
。这样的事情的另一个例子是Maybe
:
data Maybe a = Just a | Nothing
Maybe
它本身并不属于某种类型。你必须提供另一种类型,例如Maybe Int
或Maybe 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 :: *
)。
错误消息中提到了两种类型:*
是值所居住的类型,例如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
的定义在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"))))