Type constraints for automatic function constraint deduction in Haskell

老子叫甜甜 提交于 2021-02-07 13:13:52

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!