<- ^ ->
Pattern matching unions

14   Pattern matching unions

Similarly as with tuples, pattern matching is the only way to get to the value of union. Union patterns consists of field name followed by nothing, (), pattern enclosed in ( ) or tuple pattern. This mimics ways, one can construct union value.

Following example utilizes pattern matching in simple symbolic expression computations environment.

        union exp {
                int Const;
                void Var;
                *(exp, exp) Add;
                *(exp, exp) Sub;
                *(exp, exp) Mul;
                *(exp, exp) Div;
        }
        
        int compute(exp e, int v)
        {
                switch e {
                case Const(x)    : return x;
                case Var         : return v;
                case Add(e1, e2) : return compute(e1, v) + compute(e2, v);
                case Mul(e1, e2) : return compute(e1, v) * compute(e2, v);
                case Div(e1, e2) : return compute(e1, v) / compute(e2, v);
                case Sub(e1, e2) : return compute(e1, v) - compute(e2, v);
                }
        }

        exp diff(exp e)
        {
                switch e {
                case Const(_)    : return Const(0);
                // both Var and Var() are legal
                case Var()       : return Const(1);
                case Add(e1, e2) : return Add(diff(e1), diff(e2));
                case Sub(e1, e2) : return Sub(diff(e1), diff(e2));
                case Div(e1, e2) :
                        exp up = Sub(Mul(diff(e1), e2), Mul(e1, diff(e2)));
                        exp down = Mul(e2, e2);
                        return Div(up, down);
                case Mul(e1, e2) : 
                        return Add(Mul(diff(e1), e2), Mul(e1, diff(e2)));
                }
        }

14.1   What happen to C's switch and enums?!?

Hmm... they are still there:

        union color {
                void Red;
                void Green;
                void Blue;
        }
This is roughly equivalent of C's:

        typedef enum {
                Red, 
                Green, 
                Blue 
        } color 
Then we do:

        string color_name(color c)
        {
                string r;
                
                switch (c) {
                case Red: 
                        r = "red";
                case Green: 
                        r = "green";
                // Blue() is also legal
                case Blue: 
                        r = "blue";
                }

                return r;
        }
This looks almost like C, modulo one little issue. There is no fall through. Each case is separate branch or program execution. One doesn't need to write break statements. In fact, break would break loop enclosing switch, if there is any!

14.2   Fall through disclaimer

If you don't like the no-fall-through clause, please first consider following snippet of code:

        switch e {
        case Mult(e1, e2):
                // here we do fall through
        case Const(x):
                // hmm... what does x mean, if we fall through from Mult(...)?
                // and what can e1 and e2 mean here, if we didn't fall
                // through?
        }
Because case can bind new variables, it has to introduce new scope. So fall through is impossible.

14.3   But I want fall through!

Despite disclaimer above limited form of fall-through is available, if there are no statements after case ...:. Only variables bound by all case clauses are available in clause body. Also each occurrence of given variable needs to have the same type, thus:

        union foo {
                int Int;
                int Int2;
                int Int3;
                string String;
                bool Bool;
        }

        foo x;

        // this is legal
        switch x {
        case Bool(i):     skip;         // don't fall through
        case Int(i):
        case Int2(i):
        case Int3(i):     print_int(i);
        case String(s):   print_string(s);
        case _:           skip;
        }

        // this is not legal
        switch x {
        case Bool(i):
        case Int(i):      skip;  // i cannot be int and bool at the same time
        case _:           skip;
        }

        // nor this:
        switch x {
        case Bool(_):
        case Int(i):
        case Int3(i):     print_int(i); // unbound variable i
        case _:           skip;
        }

        // but this *is* legal
        switch x {
        case Bool(_):
        case Int(i):
        case Int3(i):     skip;
        case _:           skip;
        }
<- ^ ->
Pattern matching unions