在Haskell中键入自动函数约束推导的约束

时间:2013-06-04 21:22:44

标签: haskell types type-deduction

出于教育目的,我在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. 

3 个答案:

答案 0 :(得分:8)

这是关于数据声明的上下文的众所周知的问题。如果您定义data Ord a => Tree a = ...,那么它会强制任何提及Tree a的函数都具有Ord a上下文。这并没有为您节省任何打字,但从好的方面来说,明确的背景是明确的需要。

GADTs救援!

解决方法是使用Generalised Abstract Data TypesGADTs)。

{-# 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

上下文仅来自contstructors

请记住,您只能使用其中一个构造函数获取隐式上下文。 (请记住,这是定义上下文的地方。)

这实际上是我们需要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的用法,但这并不方便,老实说。