The mixin technique in my
last post was a bit awkward to use
with generic
methods. It is much less awkward to use for classes, however. For example, if
you want an expression evaluator much like I posted
here, but you want to leave the result type open, you can use this technique
to do it.
First, I declared a policy interface for the operators I wanted to abstract
over,
partial interface IOperatorPolicy<T> {
T Add(T a, T b);
T Subtract(T a, T b);
T Multiply(T a, T b);
T Divide(T a, T b);
}
Second, I created the operator policies for int
and double. I could have created more,
decimal, float, etc. but
two is sufficient for demonstration.
partial struct IntOperatorPolicy : IOperatorPolicy<int> {
int IOperatorPolicy<int>.Add(int a, int b) { return a + b; }
int IOperatorPolicy<int>.Subtract(int a, int b) { return a - b; }
int IOperatorPolicy<int>.Multiply(int a, int b) { return a * b; }
int IOperatorPolicy<int>.Divide(int a, int b) { return a / b; }
}
partial struct DoubleOperatorPolicy : IOperatorPolicy<double> {
double IOperatorPolicy<double>.Add(double a, double b) { return a + b;
double IOperatorPolicy<double>.Subtract(double a, double b) { return a - b; }
double IOperatorPolicy<double>.Multiply(double a, double b) { return a * b; }
double IOperatorPolicy<double>.Divide(double a, double b) { return a / b; }
}
Next are the expression nodes themselves. I wrap them in a generic class
that takes the parameters. This mitigates some of the clutter caused by the
policy.
partial class Expression<T, OperatorPolicy>
where OperatorPolicy : struct, IOperatorPolicy<T> {
static OperatorPolicy _operatorPolicy = new OperatorPolicy();
public abstract partial class Expr {
public abstract T Evaluate();
}
public partial class Literal : Expr {
private T _literal;
public Literal(T literal) { _literal = literal; }
public override T Evaluate() {
return _literal;
}
public override string ToString() {
return _literal.ToString();
}
}
public abstract partial class BinaryOp : Expr {
private Expr _left;
private Expr _right;
protected BinaryOp(Expr left, Expr right) { _left = left; _right = right; }
public override T Evaluate() {
return EvaluateOp(_left.Evaluate(), _right.Evaluate());
}
protected abstract T EvaluateOp(T left, T right);
}
public partial class Add : BinaryOp {
public Add(Expr left, Expr right) : base(left, right) { }
protected override T EvaluateOp(T left, T right) {
return _operatorPolicy.Add(left, right);
}
}
public partial class Add : BinaryOp {
public Add(Expr left, Expr right) : base(left, right) { }
protected override T EvaluateOp(T left, T right) {
return _operatorPolicy.Add(left, right);
}
}
public partial class Subtract : BinaryOp {
public Subtract(Expr left, Expr right) : base(left, right) { }
protected override T EvaluateOp(T left, T right) {
return _operatorPolicy.Subtract(left, right);
}
}
public partial class Multiply : BinaryOp {
public Multiply(Expr left, Expr right) : base(left, right) { }
protected override T EvaluateOp(T left, T right) {
return _operatorPolicy.Multiply(left, right);
}
}
public partial class Divide : BinaryOp {
public Divide(Expr left, Expr right) : base(left, right) { }
protected override T EvaluateOp(T left, T right) {
return _operatorPolicy.Divide(left, right);
}
}
}
Here the Expression class acts as a container of a family of
classes. Once you have an instance of one of the Expr derived classes, calling
Evaluate() is straight-forward. Unfortunately, constructing one is a bit of a
challenge. To get a literal of the int expression you would need to do,
var i1 = new Expression<int, IntOperatorPolicy>.Literal(1);
You can make this a little better by introducing a static
method into Expression like,
public static Expr L(T literal) {
return new Literal(literal);
}
then the above becomes,
var i1 = Expression<int, IntOperatorPolicy>.L(1);
This still a bit awkward especially when you want to construct a more
complicated expression,
var e = new Expression<int, IntOperatorPolicy>.Add(i1, i1);
This can be made significantly better by adding operator overloads to Expr as
in,
public abstract partial class Expr {
public abstract T Evaluate();
public static Expr operator +(Expr a, Expr b) {
return new Add(a, b);
}
public static Expr operator -(Expr a, Expr b) {
return new Subtract(a, b);
}
public static Expr operator *(Expr a, Expr b) {
return new Multiply(a, b);
}
public static Expr operator /(Expr a, Expr b) {
return new Divide(a, b);
}
}
now the e assignment can look like,
var e = i1 + i1;
and finally, to make it look even better you can put the code to create the
expression in a static method of a class that derives from Expression and closes the
type parameters. Here are examples of one for int and
a similar one for double.
class TestInt : Expression<int, IntOperatorPolicy> {
public static void Run() {
var iv = L(1) + L(2) * L(3);
Console.WriteLine(iv.Evaluate());
}
}
class TestDouble : Expression<double, DoubleOperatorPolicy> {
public static void Run() {
var dv = L(1.2) + L(3.4) * L(5.6);
Console.WriteLine(dv.Evaluate());
}
}
This shows taking advantage of the C# compiler's operator overloading to
produce an expression tree and the use of the L() method as a sort
of cast to change a literal of T into a Literal<T>.
The call to Evaluate() calculates the result of the expression as
an int or double.
The code generated, as discussed
last time, is quite good when JIT'ing outside the debugger and using
optimized retail bits.
This use of a policy struct is what I refer to
as an adapter policy. The Expression class has an
abstraction it supports, represented by IOperatorPolicy<T>, but
this abstraction is not supported by any of the interesting types you would want
to pass as Expression's T. This is solved by creating
an adapter policy and passing that along with T. This
allows a generic type to introduce an abstraction that is unknown to the types
the generic wants to range over, allow us, as above, to pass int
for Expression's T even though int
doesn't implement IOperatorPolicy<T> itself.
Next time I will discuss another application of an adapter policy.