I don't really like the type-checking and type-casting solutions provided above, so here's 100% type-safe union which will throw compilation errors if you attempt to use the wrong datatype:

```
using System;
namespace Juliet
{
class Program
{
static void Main(string[] args)
{
Union3<int, char, string>[] unions = new Union3<int,char,string>[]
{
new Union3<int, char, string>.Case1(5),
new Union3<int, char, string>.Case2('x'),
new Union3<int, char, string>.Case3("Juliet")
};
foreach (Union3<int, char, string> union in unions)
{
string value = union.Match(
num => num.ToString(),
character => new string(new char[] { character }),
word => word);
Console.WriteLine("Matched union with value '{0}'", value);
}
Console.ReadLine();
}
}
public abstract class Union3<A, B, C>
{
public abstract T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h);
// private ctor ensures no external classes can inherit
private Union3() { }
public sealed class Case1 : Union3<A, B, C>
{
public readonly A Item;
public Case1(A item) : base() { this.Item = item; }
public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
{
return f(Item);
}
}
public sealed class Case2 : Union3<A, B, C>
{
public readonly B Item;
public Case2(B item) { this.Item = item; }
public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
{
return g(Item);
}
}
public sealed class Case3 : Union3<A, B, C>
{
public readonly C Item;
public Case3(C item) { this.Item = item; }
public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
{
return h(Item);
}
}
}
}
```

Disclaimer: A lot of this doesn't really work quite right when you account for ⊥, so I'm going to blatantly disregard that for the sake of simplicity.

A few initial points:

Note that "union" is probably not the best term for A+B here--that's specifically a *disjoint* union of the two types, because the two sides are distinguished even if their types are the same. For what it's worth, the more common term is simply "sum type".

Singleton types are, effectively, all unit types. They behave identically under algebraic manipulations and, more importantly, the amount of information present is still preserved.

You probably want a zero type as well. Haskell provides that as `Void`

. There are no values whose type is zero, just as there is one value whose type is one.

There's still one major operation missing here but I'll get back to that in a moment.

As you've probably noticed, Haskell tends to borrow concepts from Category Theory, and all of the above has a very straightforward interpretation as such:

Given objects A and B in **Hask**, their product A×B is the unique (up to isomorphism) type that allows two projections *fst* : A×B → A and *snd* : A×B → B, where given any type C and functions *f* : C → A, *g* : C → B you can define the pairing *f &&& g* : C → A×B such that *fst ∘ (f &&& g)* = *f* and likewise for *g*. Parametricity guarantees the universal properties automatically and my less-than-subtle choice of names should give you the idea. The `(&&&)`

operator is defined in `Control.Arrow`

, by the way.

The dual of the above is the coproduct A+B with injections *inl* : A → A+B and *inr* : B → A+B, where given any type C and functions *f* : A → C, *g* : B → C, you can define the copairing *f ||| g* : A+B → C such that the obvious equivalences hold. Again, parametricity guarantees most of the tricky parts automatically. In this case, the standard injections are simply `Left`

and `Right`

and the copairing is the function `either`

.

Many of the properties of product and sum types can be derived from the above. Note that any singleton type is a terminal object of **Hask** and any empty type is an initial object.

Returning to the aforementioned missing operation, in a cartesian closed category you have exponential objects that correspond to arrows of the category. Our arrows are functions, our objects are types with kind `*`

, and the type `A -> B`

indeed behaves as B^{A} in the context of algebraic manipulation of types. If it's not obvious why this should hold, consider the type `Bool -> A`

. With only two possible inputs, a function of that type is isomorphic to two values of type `A`

, i.e. `(A, A)`

. For `Maybe Bool -> A`

we have three possible inputs, and so on. Also, observe that if we rephrase the copairing definition above to use algebraic notation, we get the identity C^{A} × C^{B} = C^{A+B}.

As for *why* this all makes sense--and in particular why your use of the power series expansion is justified--note that much of the above refers to the "inhabitants" of a type (i.e., distinct values having that type) in order to demonstrate the algebraic behavior. To make that perspective explicit:

The product type `(A, B)`

represents a value each from `A`

and `B`

, taken independently. So for any fixed value `a :: A`

, there is one value of type `(A, B)`

for each inhabitant of `B`

. This is of course the cartesian product, and the number of inhabitants of the product type is the product of the number of inhabitants of the factors.

The sum type `Either A B`

represents a value from either `A`

or `B`

, with the left and right branches distinguished. As mentioned earlier, this is a disjoint union, and the number of inhabitants of the sum type is the sum of the number of inhabitants of the summands.

The exponential type `B -> A`

represents a mapping from values of type `B`

to values of type `A`

. For any fixed argument `b :: B`

, any value of `A`

can be assigned to it; a value of type `B -> A`

picks one such mapping for each input, which is equivalent to a product of as many copies of `A`

as `B`

has inhabitants, hence the exponentiation.

While it's tempting at first to treat types as sets, that doesn't actually work very well in this context--we have disjoint union rather than the standard union of sets, there's no obvious interpretation of intersection or many other set operations, and we don't usually care about set membership (leaving that to the type checker).

On the other hand, the constructions above spend a lot of time talking about *counting* inhabitants, and *enumerating* the possible values of a type is a useful concept here. That quickly leads us to enumerative combinatorics, and if you consult the linked Wikipedia article you'll find that one of the first things it does is define "pairs" and "unions" in exactly the same sense as product and sum types by way of generating functions, then does the same for "sequences" that are identical to Haskell's lists using exactly the same technique you did.

**Edit:** Oh, and here's a quick bonus that I think demonstrates the point strikingly. You mentioned in a comment that for a tree type `T = 1 + T^2`

you can derive the identity `T^6 = 1`

, which is clearly wrong. However, `T^7 = T`

*does* hold, and a bijection between trees and seven-tuples of trees can be constructed directly, cf. Andreas Blass's "Seven Trees in One".

**Edit×2:** On the subject of the "derivative of a type" construction mentioned in other answers, you might also enjoy this paper from the same author which builds on the idea further, including notions of division and other interesting whatnot.

## Best Solution

Kotlin's

`sealed class`

approach to that problem is extremely similar to the Scala`sealed class`

and`sealed trait`

.Example (taken from the linked Kotlin article):