next up previous contents
Next: Exceptions Up: Concrete differences Previous: Unions   Contents

Pattern matching

It is especially useful with conjunction with tuples and unions. Patterns are used in switch and let statements, like this:

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

        exp diff(exp e)
        {
                switch e {
                case Const[x]    : return Const[0];
                case Var[x]      : 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])];
                }
        }

To convince you, we're not so far from C:

        union color {
                void Red;
                void Green;
                void Blue;
        }

This is roughly equivalent of C's: `typedef enum Red, Green, Blue color'. Then we do:

        int spy007;
        
        string color_name(color c)
        {
                string r;
                
                switch (c) {
                case Red: 
                        spy007++;
                        r = "red";
                case Green: 
                        r = "green";
                // [] also allowed, if you don't know why, just don't use
                // it :)
                case Blue[]: 
                        r = "blue";
                }

                return r;
        }

You can note lack of break at the end of case clauses. They are not required, as there is no fall through (because case clause has to introduce new scope).

In previous examples we have checked for each possibility, but we don't have to:

        string var_name1(exp e) 
        {
                switch e {
                case Var[x]: return x;
                // matches any x
                case x: return "not variable";
                }
        }

        string var_name2(exp e) 
        {
                switch e {
                case Var[x]: return x;
                }
        }

var_name2 would raise Match_failure exception if any pattern didn't match.

Patterns can be used to decomposite tuples, like this:

        *[int, int] t = [1, 2];

        ...
        switch t {
        case [i1, i2]: return i1 + i2;
        }

This can be abbreviated to:

        let [i1, i2] = t in { return i1 + i2; }

There can be more then one assignment, like this:

        let [t1, t1] = t,
            [s1, s2] = s {
                // ...
        }

The let assignment and binding names with case just creates new name for an object. Specificly it means that assigning values to names bound with let/case changes object itself. Example:

        *[int, string] t = [1, "one"];
   
        switch t {
        case [i, s]: i = 2; 
        }
   
        let [i, s] = t in { s = "two"; }

        // here t == [2, "two"]

One can note that you can also decomposite t with:

        string s;
        int i;
        [i, s] = t;
        // here i = 2, s = "two"
        // however:
        i = 3; s = "three";
        // here i = 3, s = "three", but t == [2, "two"]

You can also pattern-match structures [[describe this]]

[[This would be nice, but... not yet]]


next up previous contents
Next: Exceptions Up: Concrete differences Previous: Unions   Contents
Micha³ Moskal 2001-11-27