Full pattern matching

Pattern matching allows complex data types to be analysed with a minimum of code, and without the need for nested if statements. This page gives some examples.

3-dimensional co-ordinates

data Loc(Int x, Int y, Int z);

String describeLocation(Loc location) {
    case location of {
         Loc(0,0,0) -> return "At origin";
        |Loc(_,0,0) -> return "On X axis";
        |Loc(0,_,0) -> return "On Y axis";
        |Loc(0,0,_) -> return "On Z axis";
        |Loc(0,_,_) -> return "On Y-Z plane";
        |Loc(_,0,_) -> return "On X-Z plane";
        |Loc(_,_,0) -> return "On X-Y plane";
        |_ -> return "Nowhere special";
    }
}

The same function, using nested ifs, is much less readable:

String describeLocation2(Loc location) {
    if (location.x == 0) {
        if (location.y == 0) {
            if (location.z == 0) {
                return "At origin";
            } else {
                return "On Z axis";
            }
        } else if (location.z == 0) {
            return "On Y axis";
        } else {
            return "On Y-Z plane";
        }
    } else if (location.y == 0) {
        if (location.z == 0) {
            return "On X axis";
        } else {
            return "On X-Z plane";
        }
    } else if (location.z == 0) {
        return "On X-Y plane";
    } else {
        return "Nowhere special";
    }

}

Pattern matching can also be used to extract data from data structures as they are matched. The example below demonstrates a few ways to extract elements.

data Line(Loc start, Loc end, Direction dir);
data Direction = Unidirectional | Bidirectional;

Maybe<Loc> reachableFromOrigin(Line line) {
    case line of {
        Line(Loc(0,0,0),target,_) -> return just(target);
// next line extracts the co-ordinates rather than just using target
// to demonstrate that it is possible
      | Line(Loc(x,y,z),Loc(0,0,0),Bidirectional) -> return just(Loc(x,y,z));
      | _ -> return nothing;
    }
}

The data extraction capability doesn't require a case statement:

line = Line(Loc(0,0,0),Loc(1,2,3),Unidirectional);
let Line(start,Loc(endx,endy,_),_) = line;
// start = Loc(0,0,0)
// endx = 1
// endy = 2

Pattern matching may also be used on lists. This pattern match checks that a set of three lines (already known to be unidirectional) describes a triangle with one point at the origin:

Bool isOriginCentredTriangle([Line] lines) {
    case lines of {
        [Line(Loc(0,0,0),pt1a,_),Line(pt1b,pt2a,_),Line(pt2b,Loc(0,0,0),_)] ->
            return (pt1a == pt1b && pt2a == pt2b);
      | [_,_,_] -> return false; 
      | _ -> throw(TrianglesMustHaveExactlyThreeLines); // wrong list size
    }
}

Linked lists

A linked list can be represented easily as:

data List<a> = nil | cons(a,List<a>);

The following function uses pattern matching, first-class functions and recursion to delete the first element matching a conditional from the list, and return it.

Exception NothingInListMatches; // in case of failure
// uses var because the list parameter will be modified
a deleteMatching(var List<a> xs, Bool(a) matching) {
    case xs of {
        nil -> throw(NothingInListMatches);
	| cons(x,ys) -> if (matching(x)) {
            xs = ys;
	    return x;
        } else {
            return deleteMatching(ys,matching);
        }
    }
}

The example uses many of the features of Kaya and you may find it useful to read the tutorials if you are unfamiliar with the syntax. These features allow the code to be written clearly and concisely, reducing the chances of errors.

Expression evaluator

This code evaluates simple integer expressions (which may have been generated by parsing a String). First, data types are defined:

// simple operators (just an enumeration)
data Op = Plus | Minus | Times | Divide;
// expressions
data Expr = Result(Int) | Infix(Op,Expr,Expr);

The expression evaluator is then very short.

Int eval(Expr expr) {
    case expr of {
        Result(i) -> return i;
	| Infix(Plus,p1,p2) -> return eval(p1) + eval(p2);
	| Infix(Minus,m1,m2) -> return eval(m1) - eval(m2);
	| Infix(Times,t1,t2) -> return eval(t1) * eval(t2);
	| Infix(Divide,d1,d2) -> return eval(d1) / eval(d2);
    }
}
kaya@kayalang.org | Last modified 4 September 2008 | Supported by Durham CompSoc | Powered by Kaya