A two-pronged solution to the expression problem.
Open functions Instead of pattern-matching on cases, a function pattern matches on a sum, with each disjunct being a class (corresponding to a case). When a function is declared open, you can add new definitions of that function, pattern matching on a set of classes disjoint from those of existing definitions for the function.
Enriched classes You can use enriched classes, which contain additional members to be added to a class. Enrichments differ from mixins in that the enrichment is done recursively over appearances of the class in the class definition. For any function Foo f(Foo x) there is an equivalent function Foo:E f(Foo:E x) resulting from enriching the type, and the language allows you to use the old function at the new type.
Application to the standard example, starting with a data-centred approach:
abstract open class Expr { int eval(); } class Num : Expr { int n; int eval() { return n; } } class Plus : Expr { Expr expr1, expr2; int eval() { return expr1.eval() + expr2.eval(); } }We can use enriched classes to add a print function:
enrichment Print { String print(); } enrichment Num : Print { String print() { return printf("%i", n); } } enrichment Plus : Print { String print() { return expr1.print() + " + " + expr2.print(); } }And use print on an instance of Expr:Print.
Now starting with an operation-centred approach:
abstract class Expr { } class Num : Expr { int n; } class Plus : Expr { Expr expr1, expr2; } open int eval(Expr expr) { switch (expr) { case Num n: return n; case Expr expr1 expr2: return eval(expr1) + eval(expr2); } }We can use open functions to add a new case:
class Neg : Expr { Expr expr; } int eval(Expr expr) { switch (expr) { case Neg expr: return -eval(expr); } }--JamesCandy