Haskell中自动函数约束推论的类型约束 [英] Type constraints for automatic function constraint deduction in Haskell

查看:55
本文介绍了Haskell中自动函数约束推论的类型约束的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

出于教育目的,我在Haskell的树木旁玩耍.我有这样定义的 Tree a 类型

For educational purposes I am playing around with trees in Haskell. I have Tree a type defined like this

data Tree a = EmptyTree | Node a (Tree a) (Tree a)

和许多具有基本约束的函数- Ord a -因此它们具有类似

and a lot of functions that share a basic constraint - Ord a - so they have types like

treeInsert :: Ord a => a -> Tree a -> Tree a
treeMake :: Ord a => [a] -> Tree a

,依此类推.我也可以像这样定义 Tree a

and so on. I can also define Tree a like this

data Ord a => Tree a = EmptyTree | Node a (Tree a) (Tree a)

但是我不能简化我的功能,并省略以下额外的 Ord a :

but I can not simplify my functions and omit the extra Ord a to be as the following:

treeInsert :: a -> Tree a -> Tree a
treeMake :: [a] -> Tree a

为什么Haskell(与 -XDatatypeContexts 一起运行)不能自动推断出此约束?对我来说似乎应该很明显.我为什么错了?

Why does Haskell (running with -XDatatypeContexts) not automatically deduce this constraint? It seems to to be quite obvious for me that it should. Why am I wrong?

这是一些示例源代码

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. 

推荐答案

这是关于数据声明上下文的众所周知的陷阱.如果定义 data Ord,则= =>.Tree a = ... 的所有作用是强制任何提及 Tree a 的函数具有 Ord a 上下文.这不会节省您任何键入的费用,但从正面看,明确的上下文可以清楚地了解所需的内容.

This is a well-known gotcha about contexts for data declaration. If you define data Ord a => Tree a = ... all it does is force any function that mentions Tree a to have an Ord a context. This doesn't save you any typing, but on the plus side an explicit context is clear about what's needed.

解决方法是使用我们可以通过提供显式类型签名将上下文直接放在构造函数上:

We can put the context on the constructor directly, by providing an explict type signature:

data Tree a where
   EmptyTree :: (Ord a,Eq a) => Tree a
   Node :: (Ord a,Eq a) => a -> Tree a -> Tree a -> Tree a

,然后每当我们与 Node左右匹配进行模式匹配时,我们都会得到一个隐式的(Ord a,Eq a)上下文,就像您想要的那样,因此我们可以开始像这样定义 treeInsert :

and then whenever we pattern match with Node a left right we get an implicit (Ord a,Eq a) context, just like you want, so we can start to define treeInsert like this:

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) 

派生东西

如果仅在其中添加 driving 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 扩展名( {-#语言GADT,GADTSyntax,StandaloneDeriving#-} ),那么我们可以像

That's a pain, but like it says, if we add the StandaloneDeriving extension ({-# Language GADTs, GADTSyntax, StandaloneDeriving #-}) we can then do stuff like

deriving instance Show a => Show (Tree a)
deriving instance Eq (Tree a) -- wow

,一切正常.哇,是因为隐式的 Eq a 上下文意味着我们不需要实例上的显式的 Eq a .

and everything works out ok. The wow was because the implicit Eq a context means we don't need an explicit Eq a on the instance.

请记住,您只能通过使用构造函数之一来获取隐式上下文.(请记住,这是定义上下文的地方.)

Bear in mind that you only get the implicit context from using one of the constructors. (Remember that's where the context was defined.)

这实际上就是为什么我们需要 EmptyTree 构造函数上的上下文的原因.如果我们只是将 EmptyTree :: Tree a 放到行中

This is actually why we needed the context on the EmptyTree constructor. If we'd just put EmptyTree::Tree a, the line

treeInsert a EmptyTree = Node a EmptyTree EmptyTree

在等式的左侧将没有(Ord a,Eq a)上下文,因此右侧将缺少这些实例,而对于 Node 构造函数.那将是一个错误,因此在这种情况下保持上下文一致很有帮助.

wouldn't have an (Ord a,Eq a) context from the left hand side of the equation, so the instances would be missing from the right hand side, where they're needed for the Node constructor. That would be an error, so it's helpful in this case to keep the contexts consistent.

这也意味着您不能拥有

treeMake :: [a] -> Tree a

treeMake xs = foldr treeInsert EmptyTree xs

您将得到一个没有针对(Ord a)错误的实例,因为左侧没有构造函数为您提供(Ord a,Eq a)上下文.

you'll get a no instance for (Ord a) error, because there's no constructor on the left hand side to give you the (Ord a,Eq a) context.

这意味着您仍然需要

treeMake :: Ord a => [a] -> Tree a

抱歉,这次没有办法,但是从好的方面来说,这很可能是您要编写的唯一没有树参数的树函数.大多数树函数将在定义的左侧生成一棵树并对其进行处理,因此大多数时候您将具有隐式上下文.

There's no way round it this time, sorry, but on the plus side, this may well be the only tree function you'll want to write with no tree argument. Most tree functions will take a tree on the left hand side of the definition and do somthing to it, so you'll have the implicit context most of the time.

这篇关于Haskell中自动函数约束推论的类型约束的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆