Search code examples
pattern-matchingrascal

Rascal pattern guards


Does Rascal support pattern guards?

I'm trying to order subexpressions on build:

data Expr = and(Expr l, Expr r) | ... ;
Expr and(l, r) | r < l = and(r, l); // fictional syntax for "r < l" guard

If guards are unsupported, what's the best way to implement the above?


Solution

  • Just to simplify things I've used int as the type for l and r at first. What you want to do is something like the following:

    rascal>data Expr = and(int l, int r);
    ok
    
    rascal>Expr and(int l, int r) = and(r,l) when r < l;
    Expr (int, int): rascalfunction()
    
    rascal>and(5,6);
    Expr: and(5,6)
    
    rascal>and(6,5);
    Expr: and(5,6)
    

    If you create a function with the same name as the constructor that returns the data type you are defining (e.g., Expr), this will allow you to do this kind of canonicalization. The when clause says to only apply the function when the condition matches. You could have a more complex condition, so if you had Expr instead as the type of the fields you could have a function like eval that actually evaluates an expression and returns a result. A partial example of this (which assumes you only have one level of nesting) is the following:

    rascal>data Expr = number(int n) | and(Expr l, Expr r);
    ok
    
    rascal>int eval(number(int n)) = n;
    int (Expr): rascalfunction()
    
    rascal>Expr and(Expr l, Expr r) = and(r,l) when eval(r) < eval(l);
    Expr (Expr, Expr): rascalfunction()
    
    rascal>and(number(5),number(6));
    Expr: and(
      number(5),
      number(6))
    
    rascal>and(number(6),number(5));
    Expr: and(
      number(5),
      number(6))
    

    To use when, you need to use this expression form of a function, where you have an = sign and the return value. If you have a more complex computation in the canonicalization, you can instead do something like the following:

    Expr and(int l, int r) { 
        if (r < l) {
            return and(r,l); 
        } else {
            fail; 
        }
    }
    

    This essentially does the same thing. If the condition is met, a new version of and is returned. If the condition is not met, fail is used to indicate that we don't actually want to apply this function, so the initial value will be returned:

    rascal>and(5,6);
    Expr: and(5,6)
    
    rascal>and(6,5);
    Expr: and(5,6)