ℂDuce: Documentation: Tutorial: Polymorphism in ℂDuce (development only)
What follows is code that can be executed in the current development version but it is
not included in the stable version yet. The syntax is not finalized and may change before inclusion in a stable release.
Deep flatten
In ℂDuce there is a flatten operator that transforms a sequence of sequences into a sequence. To show the power of the polymorphic typing of ℂDuce let us define the deep variant of flatten, that we call flatten_d, which recursively flattens all nested sequences into a single flat one. Nested sequences are a convenient representation of trees, whose leaves are values that are not sequences. So let us first define the type of α-trees, that is of trees whose leaves are of type α:type Tree('a) = ('a\[Any*]) | [ (Tree('a))* ]
let flatten_d ( (Tree('a)) -> ['a*] ) | [] -> [] | [h ; t] -> (flatten_d h)@(flatten_d t) | x -> [x]
ℂDuce is the only language that can type this function truly polymorphically, in the sense that the function can be applied to any well typed expression. Indeed, notice that it is possible to unify the type Tree('a) with every type (in particular Any is a subtype of Tree(Any)). Therefore flatten_d can be applied to every well-typed expression. So for instance we have:
# flatten_d [ 3 'r' [ 4 [ `true 5 ]] [ "quo" [[ `false ] "stop" ] ] ];; - : [ (Bool | 3--5 | 'o'--'u')* ] = [ 3 'r' 4 true 5 'quo' false 'stop' ]
If we want to define a version of deep flatten that can be applied only to sequences, then this can be done by the following further definition
let flatten_tree (x: [(Tree('a))*]) : ['a*] = flatten_d x;;
Partial applications
ℂDuce type inference is pretty sofisticated. While in ML-like languages unification suffices to type the application of a function to an argument, to type the same application in ℂDuce requires to combine unification with subtyping and expansion. As an example consider the following definitionlet even (Int -> Bool; ('a \ Int) -> ('a \ Int)) | x&Int -> x mod 2 = 0 | x -> x;;
let fmap (f : 'a -> 'b) (l : ['a*]) : ['b*] = match l with | [] -> [] | (a,b) -> (f a, fmap f b)
# fmap even;; - : ([ Int* ] -> [ Bool* ]) & ([ ('a \ Int)* ] -> [ ('a \ Int)* ]) & ([ ] -> [ ]) & ([ (Int | ('b \ Int))* ] -> [ (Bool | ('b \ Int))* ]) = <fun>
While the definition of even is somewhat artificial, we show next that its type is not strange, since it follows a common pattern that, for instance, is used to type the balance function we present next.
Red-Black trees
As a second example we show that the use of polymorphic set-theoretic types yields a better definition of Okasaki's implementation of red-black trees that can be found in his excellent monograph [1].A red and black tree is a colored binary search tree in which all nodes are colored either black or red and that satisfies 4 invariants:
- the root of the tree is black;
- the leaves of the tree are black;
- no red node has a red child;
- very path from the root to a leaf contains the same number of black nodes.
type RBtree(α) = Btree(α) | Rtree(α) (* Black rooted RB tree: *) type Btree(α) = [] | <black elem=α>[ RBtree(α) RBtree(α) ] (* Red rooted RB tree: *) type Rtree(α) = <red elem=α>[ Btree(α) Btree(α) ] type Wrongtree(α) = <red elem=α>( [ Rtree(α) Btree(α) ] | [ Btree(α) Rtree(α) ]) type Unbalanced(α) = <black elem=α>( [ Wrongtree(α) RBtree(α) ] | [ RBtree(α) Wrongtree(α) ]) let balance ( Unbalanced(α) -> Rtree(α) ; β\Unbalanced(α) -> β\Unbalanced(α) ) | <black (z)>[ <red (y)>[ <red (x)>[ a b ] c ] d ] | <black (z)>[ <red (x)>[ a <red (y)>[ b c ] ] d ] | <black (x)>[ a <red (z)>[ <red (y)>[ b c ] d ] ] | <black (x)>[ a <red (y)>[ b <red (z)>[ c d ] ] ] -> <red (y)>[ <black (x)>[ a b ] <black (z)>[ c d ] ] | x -> x let insert (x : α) (t : Btree(α)) : Btree(α) = let ins_aux ( [] -> Rtree(α); Btree(α)\[] -> RBtree(α)\[]; Rtree(α) -> Rtree(α)|Wrongtree(α) ) | [] -> <red elem=x>[ [] [] ] | (<(color) elem=y>[ a b ]) & z -> if x << y then balance <(color) elem=y>[ (ins_aux a) b ] else if x >> y then balance <(color) elem=y>[ a (ins_aux b) ] else z in match ins_aux t with | <_ (y)>[ a b ] -> <black (y)>[ a b ]
The insert function takes an element x of type α, and returns a function that maps red-black trees into red-black trees.
insert :: α -> Btree(α) -> Btree(α)
balance :: ( Unbalanced(α) -> Rtree(α) ) & ( β\Unbalanced(α) -> β\Unbalanced(α) )
ins_aux :: ( [] -> Rtree(α) ) & ( Btree(α)\[] -> RBtree(α)\[] ) & ( Rtree(α) -> Rtree(α)|Wrongtree(α) )
ins_aux :: ( Btree(α)\[] -> RBtree(α)\[] ) & ( Rtree(α)|[] -> Rtree(α)|Wrongtree(α) )
The implementation above must be compared with the corresponding version in monomorphic ℂDuce:
type RBtree = Btree | Rtree;; type Btree = [] | <black elem=Int>[ RBtree RBtree ];; type Rtree = <red elem=Int>[ Btree Btree ];; type Wrongtree = Wrongleft | Wrongright;; type Wrongleft = <red elem=Int>[ Rtree Btree ];; type Wrongright = <red elem=Int>[ Btree Rtree ];; type Unbalanced = <black elem=Int>([ Wrongtree RBtree ] | [ RBtree Wrongtree ]) let balance ( Unbalanced -> Rtree ; Rtree -> Rtree ; Btree\[] -> Btree\[] ; [] -> [] ; Wrongleft -> Wrongleft ; Wrongright -> Wrongright) | <black (z)>[ <red (y)>[ <red (x)>[ a b ] c ] d ] | <black (z)>[ <red (x)>[ a <red (y)>[ b c ] ] d ] | <black (x)>[ a <red (z)>[ <red (y)>[ b c ] d ] ] | <black (x)>[ a <red (y)>[ b <red (z)>[ c d ] ] ] -> <red (y)>[ <black (x)>[ a b ] <black (z)>[ c d ] ] | x -> x let insert (x : Int) (t : Btree) : Btree = let ins_aux ( [] -> Rtree ; Btree\[] -> RBtree\[]; Rtree -> Rtree|Wrongtree) | [] -> <red elem=x>[ [] [] ] | (<(color) elem=y>[ a b ]) & z -> if x << y then balance <(color) elem=y>[ (ins_aux a) b ] else if x >> y then balance <(color) elem=y>[ a (ins_aux b) ] else z in match ins_aux t with | <_ (y)>[ a b ] -> <black (y)>[ a b ]
ℂDuce: Documentation: Tutorial: Polymorphism in ℂDuce (development only)