出于教育目的,我在Haskell玩弄树木。我有像这样定义的Tree a
类型
data Tree a = EmptyTree | Node a (Tree a) (Tree a)
和许多共享基本约束的函数 - Ord a
- 因此它们具有类似
treeInsert :: Ord a => a -> Tree a -> Tree a
treeMake :: Ord a => [a] -> Tree a
等等。我也可以像这样定义Tree a
data Ord a => Tree a = EmptyTree | Node a (Tree a) (Tree a)
但是我无法简化我的功能,省略了额外的Ord a
如下:
treeInsert :: a -> Tree a -> Tree a
treeMake :: [a] -> Tree a
为什么Haskell(使用-XDatatypeContexts
运行)不会自动推断出这个约束?对我来说,这应该是显而易见的。为什么我错了?
以下是一些示例源代码
data (Eq a, Ord a) => Tree a = EmptyTree | Node a (Tree a) (Tree a)
treeInsert :: a -> Tree a -> Tree a
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
treeInsert a node@(Node v left right)
| a == v = node
| a > v = Node v left (treeInsert a right)
| a < v = Node v (treeInsert a left) right
mkTree :: [a] -> Tree a
mkTree [] = EmptyTree
mkTree (x:xs) = treeInsert x (mkTree xs)
我收到了这个
Tree.hs:5:26:
No instance for (Ord a)
arising from a use of `Node'
In the expression: Node a EmptyTree EmptyTree
In an equation for `treeInsert':
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
Failed, modules loaded: none.
答案 0 :(得分:8)
这是关于数据声明的上下文的众所周知的问题。如果您定义data Ord a => Tree a = ...
,那么它会强制任何提及Tree a
的函数都具有Ord a
上下文。这并没有为您节省任何打字,但从好的方面来说,明确的背景是明确的需要。
解决方法是使用Generalised Abstract Data Types(GADTs
)。
{-# Language GADTs, GADTSyntax #-}
我们可以通过提供一个explict类型签名直接将上下文放在构造函数上:
data Tree a where
EmptyTree :: (Ord a,Eq a) => Tree a
Node :: (Ord a,Eq a) => a -> Tree a -> Tree a -> Tree a
然后每当我们模式与Node a left right
匹配时,我们就会得到一个隐含的(Ord a,Eq a)
上下文,就像你想要的那样,所以我们可以开始像这样定义treeInsert
:
treeInsert :: a -> Tree a -> Tree a
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
treeInsert a (Node top left right)
| a < top = Node top (treeInsert a left) right
| otherwise = Node top left (treeInsert a right)
如果您只是在其中添加deriving Show
,则会收到错误消息:
Can't make a derived instance of `Show (Tree a)':
Constructor `EmptyTree' must have a Haskell-98 type
Constructor `Node' must have a Haskell-98 type
Possible fix: use a standalone deriving declaration instead
In the data type declaration for `Tree'
这很痛苦,但就像它说的那样,如果我们添加StandaloneDeriving
扩展程序({-# Language GADTs, GADTSyntax, StandaloneDeriving #-}
),我们就可以执行类似
deriving instance Show a => Show (Tree a)
deriving instance Eq (Tree a) -- wow
一切正常。哇是因为隐式Eq a
上下文意味着我们不需要在实例上使用显式Eq a
。
请记住,您只能使用其中一个构造函数获取隐式上下文。 (请记住,这是定义上下文的地方。)
这实际上是我们需要EmptyTree
构造函数的上下文的原因。如果我们只是放EmptyTree::Tree a
,那么
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
从等式的左侧不会有(Ord a,Eq a)
上下文,因此右侧缺少实例,Node
构造函数需要它们。这将是一个错误,因此在这种情况下保持上下文一致是有帮助的。
这也意味着你不能拥有
treeMake :: [a] -> Tree a
treeMake xs = foldr treeInsert EmptyTree xs
您会收到no instance for (Ord a)
错误,因为左侧没有构造函数可以为您提供(Ord a,Eq a)
上下文。
这意味着你仍然需要
treeMake :: Ord a => [a] -> Tree a
这次没有办法绕过它,抱歉,但从好的方面来说,这可能是你想要写的唯一没有树参数的树函数。 大多数树函数将在定义的左侧使用树,并对其做一些事情,因此大多数时候您将拥有隐式上下文。
答案 1 :(得分:2)
kirelagin关于DatatypeContexts
没用是正确的。您仍然必须在所有函数中编写类约束。但是如果你有很多课程可以让你只有一个课程,那么这里有点破解。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving Show
class (Eq a, Ord a) => Foo a where
instance (Eq a, Ord a) => Foo a where
treeInsert :: Foo a => a -> Tree a -> Tree a
treeInsert a EmptyTree = Node a EmptyTree EmptyTree
treeInsert a node@(Node v left right)
| a == v = node
| a > v = Node v left (treeInsert a right)
| a < v = Node v (treeInsert a left) right
mkTree :: Foo a => [a] -> Tree a
mkTree [] = EmptyTree
mkTree (x:xs) = treeInsert x (mkTree xs)
现在Foo
类与Eq && Ord
类似。使用类似的示例,您可以在所有函数中仅使用一个类替换所有类。正如@luqui指出的那样,您只需使用ConstraintKinds
即可使其正常工作。
或者您可以使用我认为允许您在数据定义中提及类约束的GADT。
答案 2 :(得分:0)
嗯,问题是这个约束适用于构造函数,而不适用于整个数据类型。这就是DatatypeContexts
实际上几乎无用的原因......你可以阅读更多关于here的信息。
如果您希望第二段包含解决方案,那么您运气不好,不幸的是。我不知道这样的解决方案,它似乎确实does not exist。维基文章提到MultiParamTypeClasses
的用法,但这并不方便,老实说。