Reputation: 280
I'm trying to implement a binary tree in which every node can hold information of type 'a or type 'b. The simple solution is to use 2 constructors like this:
datatype ('a, 'b) Tree = Lf
| Br1 of 'a * (('a, 'b) Tree) * (('a, 'b) Tree)
| Br2 of 'b * (('a, 'b) Tree) * (('a, 'b) Tree);
Br1(100,Lf,Br2("hello",Lf,Lf));
>val it = Br1 (100, Lf, Br2 ("hello", Lf, Lf)): (int, string) Tree;
However, i'd like to use 1 constructor, so that the result would be the following:
Br(100,Lf,Br("hello",Lf,Lf));
>val it = Br (100, Lf, Br ("hello", Lf, Lf)): (int, string) Tree;
Pattern matching doesn't seem to work, it returns a long type clash error upon calling Br:
datatype ('a, 'b) Tree = Lf
| Br of 'a * (('a, 'b) Tree) * (('a, 'b) Tree)
| Br of 'b * (('a, 'b) Tree) * (('a, 'b) Tree);
I had a feeling it had something to do with a union datatype, so i tried the following, but when i try to call Br like this, it gives an error:
local
datatype ('a,'b) u = t1 of 'a
| t2 of 'b;
in
datatype ('a, 'b) Tree = Lf
| Br of ('a,'b) u * (('a, 'b) Tree) * (('a, 'b) Tree);
end;
Br(100,Lf,Br("hello",Lf,Lf));
Elaboration failed: Unbound type "u".
Maybe the syntax is incorrect, or my idea is wrong?
Upvotes: 1
Views: 526
Reputation: 66459
You are very close.
Since your union type is local
, you can't use it at all outside the definition of ('a, 'b) Tree
.
This problem is easily solved – make it not local:
datatype ('a,'b) u = t1 of 'a
| t2 of 'b;
datatype ('a, 'b) Tree = Lf
| Br of ('a,'b) u * (('a, 'b) Tree) * (('a, 'b) Tree);
(The u
type is very useful in general, and is usually called "either", or sometimes "variant". I have no idea why it's not in the SML Basis Library.)
A second problem is that you need to use the constructors of u
to create values of u
, like everywhere else:
- Br(t1 100,Lf,Br(t2 "hello",Lf,Lf));
val it = Br (t1 100,Lf,Br (t2 #,Lf,Lf)) : (int,string) Tree
There is no way to avoid the explicit construction of values.
(It's impossible for anyone to guess whether int
is the t1
or t2
type; (int, string) u
and (string, int) u
are different types.)
Upvotes: 2
Reputation: 16145
the constructor for the non leaf nodes should be singular and without added information from the user, like the type of the value he wants to build the tree with. I thought my result example explained it.
I'll provide another answer, since both answers given now have not been satisfactory with respect to this constraint. As I commented, if you want a single constructor that takes either an int or a string, you want ad-hoc polymorphism rather than parametric polymorphism.
There is some similarity with this OCaml question: Make OCaml function polymorphic for int lists and float lists -- the main difference is that that question wants a function and this question wants a data type definition. As Jeffrey Scofield points out (adapting types to int and string in your case):
The only common type for
int
andstring
is'a
, i.e., any type at all. Since the value can be anything, there are no specific operations you can apply to it. So there's no straightforward way to write the function you want.
But as I also answer, OCaml has an experimental extension called "modular implicits" that lets you write functions that take modules as arguments, and in those modules you can provide a type parameter, along with a function interface that is common among the two or more types. I'm not aware of something similar for Standard ML.
What you're asking for could be classified as a heterogenous tree, and in Haskell you'd make that with either of the ExistentialQuantification
or GADTs
GHC extensions. It is often the case that some of the overloading mechanisms of Haskell have some comparable use in the ML module system, but in the case of GADTs, the best I was able to find is encoding of GADTs in the ML module system using Church numerals, so this is more of a proof-of-concept than a practical solution.
Upvotes: 0
Reputation: 16145
a binary tree in which every node can hold information of type 'a or type 'b
While you can do this with a single binary tree type, I would split this into two data types: The tree type, and the "either 'a
or 'b
type", since both of these are canonical data types, meaning they are recognizable by functional programmers.
datatype 'a tree = Leaf | Branch of 'a * 'a tree * 'a tree
datatype ('b, 'c) either = One of 'b | Other of 'c
val someTree = Branch (One 100, Leaf, Branch (Other "hello", Leaf, Leaf))
The type of this tree is (int, string) either tree
.
When prefixing a value with One
it takes a value of type 'b
and when prefixing a value with Other
it takes a value of type 'c
. Note that they could have been named 'a
and 'b
here, but I thought that giving them new variable names would lessen the confusion when substituting 'a'
with ('b, 'c) either
.
(Note also that typically this ('b, 'c) either
type has constructors named Left
and Right
, but I changed this since "left" and "right" also has meaning to binary trees, which would probably add confusion. The direction of the tree is still determined by the position, so the first 'a tree
is the left sub-tree and the second 'a tree
is the right sub-tree.)
You could combine the two data types into a single definition like so:
datatype ('a, 'b) eithertree =
Leaf
| BranchA of 'a * ('a, 'b) eithertree * ('a, 'b) eithertree
| BranchB of 'b * ('a, 'b) eithertree * ('a, 'b) eithertree
val anotherTree = BranchA (100, Leaf, BranchB ("hello", Leaf, Leaf))
Some considerations:
eithertree
since it combines two concepts.tree
type constructor took only an 'a
type parameter. Now eithertree
takes ('a, 'b)
because the choosing-between-'a
-and-'b
mechanism has been embedded into the tree type. BranchA
naturally assumes that its first argument is an 'a
, where Branch
must explicitly have One
in front of the 'a
every time (and likewise for 'b
and Other
).('a, 'b)
and not just a single type parameter like the first 'a tree
did. This is mainly a drawback when having to define the data type, I think. And this is probably the reason you got stuck.('a, 'b) eithertree
is less composable: Let's say you build a bunch of binary-tree traversal functions. These won't work on ('a, 'b) eithertree
because they're not 'a tree
s. But an ('a, 'b) either tree
is an 'a tree
with 'a
being ('a, 'b) either
. So you're able to reuse less code.Upvotes: 1