问题
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)
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
and so on. I can also define Tree a
like this
data Ord a => Tree a = EmptyTree | Node a (Tree a) (Tree 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
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?
Here is some example source code
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)
I am getting this
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.
回答1:
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.
GADTs to the rescue!
The workaround is to use Generalised Abstract Data Types (GADTs
).
{-# Language GADTs, GADTSyntax #-}
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
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)
Deriving stuff
If you just add deriving Show
there, you get an error:
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'
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
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.
The context only comes from contstructors
Bear in mind that you only get the implicit context from using one of the constructors. (Remember that's where the context was defined.)
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
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.
This also means that you can't have
treeMake :: [a] -> Tree a
treeMake xs = foldr treeInsert EmptyTree xs
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.
That means you still need
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.
回答2:
kirelagin is right about DatatypeContexts
being useless. You still will have to write class constrains in all the functions. But here is a little hack if you have lots of classes lying around which allows you to get away with only one class.
{-# 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)
Now Foo
class is like Eq && Ord
. Using the similar example you can replace all your classes with just a single class in all functions. As pointed by @luqui you can just use ConstraintKinds
to make it work too.
Or you can use GADTs which I think allow you to mention class constraints in data definition.
回答3:
Well, the issue is that this constraint applies to the constructor, not to the datatype as a whole. That's why DatatypeContexts
are actually almost useless… You can read more about it here.
If you were hoping that the second paragraph contains a solution, then you are out of luck, unfortunatelly. I'm not aware of such a solution, and it seems that it indeed does not exist. The wiki article mentions usage of MultiParamTypeClasses
instead, but that's not as convenient, honestly.
来源:https://stackoverflow.com/questions/16927710/type-constraints-for-automatic-function-constraint-deduction-in-haskell