Structurally Enforcing No Red Children Of Red Node

时间:2016-04-04 18:36:51

标签: haskell red-black-tree recursive-datastructures

While studying Learn You A Haskell For Great Good and Purely Functional Data Structures, I thought to try to reimplement a Red Black tree while trying to structurally enforce another tree invariant.

Paraphrasing Okasaki's code, his node looks something like this:

import Data.Maybe

data Color = Red | Black

data Node a = Node {
    value :: a,
    color :: Color,
    leftChild :: Maybe (Node a),
    rightChild :: Maybe (Node a)}

One of the properties of a red black tree is that a red node cannot have a direct-child red node, so I tried to encode this as the following:

import Data.Either

data BlackNode a = BlackNode {
    value :: a,
    leftChild :: Maybe (Either (BlackNode a) (RedNode a)),
    rightChild :: Maybe (Either (BlackNode a) (RedNode a))}
data RedNode a = RedNode {
    value :: a,
    leftChild :: Maybe (BlackNode a),
    rightChild :: Maybe (BlackNode a)}

This outputs the errors:

Multiple declarations of `rightChild'
Declared at: :4:5
             :8:5


Multiple declarations of `leftChild'
Declared at: :3:5
             :7:5


Multiple declarations of `value'
Declared at: :2:5
             :6:5

I've tried several modifications of the previous code, but they all fail compilation. What is the correct way of doing this?

1 个答案:

答案 0 :(得分:4)

Different record types must have distinct field names. E.g., this is not allowed:

data A = A { field :: Int }
data B = B { field :: Char }

while this is OK:

data A = A { aField :: Int }
data B = B { bField :: Char }

The former would attempt to define two projections

field :: A -> Int
field :: B -> Char

but, alas, we can't have a name with two types. (At least, not so easily...) This issue is not present in OOP languages, where field names can never be used on their own, but they must be immediately applied to some object, as in object.field -- which is unambiguous, provided we already know the type of object. Haskell allows standalone projections, making things more complicated here.

The latter approach instead defines

aField :: A -> Int
bField :: B -> Char

and avoids the issue.

As @dfeuer comments above, GHC 8.0 will likely relax this constraint.