Operators, Generics and Policies II: Adapter Policy
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,
partialinterface 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.
partialstruct 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.
partialclass Expression<T, OperatorPolicy>
where OperatorPolicy : struct, IOperatorPolicy<T> {
static OperatorPolicy _operatorPolicy = new OperatorPolicy();
publicabstractpartialclass Expr {
publicabstract T Evaluate();
}
publicpartialclass Literal : Expr {
private T _literal;
public Literal(T literal) { _literal = literal; }
publicoverride T Evaluate() {
return _literal;
}
publicoverridestring ToString() {
return _literal.ToString();
}
}
publicabstractpartialclass BinaryOp : Expr {
private Expr _left;
private Expr _right;
protected BinaryOp(Expr left, Expr right) { _left = left; _right = right; }
publicoverride T Evaluate() {
return EvaluateOp(_left.Evaluate(), _right.Evaluate());
}
protectedabstract T EvaluateOp(T left, T right);
}
publicpartialclass Add : BinaryOp {
public Add(Expr left, Expr right) : base(left, right) { }
protectedoverride T EvaluateOp(T left, T right) {
return _operatorPolicy.Add(left, right);
}
}
publicpartialclass Add : BinaryOp {
public Add(Expr left, Expr right) : base(left, right) { }
protectedoverride T EvaluateOp(T left, T right) {
return _operatorPolicy.Add(left, right);
}
}
publicpartialclass Subtract : BinaryOp {
public Subtract(Expr left, Expr right) : base(left, right) { }
protectedoverride T EvaluateOp(T left, T right) {
return _operatorPolicy.Subtract(left, right);
}
}
publicpartialclass Multiply : BinaryOp {
public Multiply(Expr left, Expr right) : base(left, right) { }
protectedoverride T EvaluateOp(T left, T right) {
return _operatorPolicy.Multiply(left, right);
}
}
publicpartialclass Divide : BinaryOp {
public Divide(Expr left, Expr right) : base(left, right) { }
protectedoverride 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,
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,
publicabstractpartialclass Expr {
publicabstract T Evaluate();
publicstatic Expr operator +(Expr a, Expr b) {
returnnew Add(a, b);
}
publicstatic Expr operator -(Expr a, Expr b) {
returnnew Subtract(a, b);
}
publicstatic Expr operator *(Expr a, Expr b) {
returnnew Multiply(a, b);
}
publicstatic Expr operator /(Expr a, Expr b) {
returnnew 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> {
publicstaticvoid Run() {
var iv = L(1) + L(2) * L(3);
Console.WriteLine(iv.Evaluate());
}
}
class TestDouble : Expression<double, DoubleOperatorPolicy> {
publicstaticvoid 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.
Operators, Generics and Policies II: Adapter Policy
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,
Second, I created the operator policies for
intanddouble. I could have created more,decimal,float, etc. but two is sufficient for demonstration.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.
Here the
Expressionclass acts as a container of a family of classes. Once you have an instance of one of theExprderived classes, callingEvaluate()is straight-forward. Unfortunately, constructing one is a bit of a challenge. To get a literal of theintexpression you would need to do,You can make this a little better by introducing a
staticmethod intoExpressionlike,then the above becomes,
This still a bit awkward especially when you want to construct a more complicated expression,
This can be made significantly better by adding operator overloads to
Expras in,now the
eassignment can look like,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
Expressionand closes the type parameters. Here are examples of one forintand a similar one fordouble.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 ofTinto aLiteral<T>. The call toEvaluate()calculates the result of the expression as anintordouble. 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
structis what I refer to as an adapter policy. TheExpressionclass has an abstraction it supports, represented byIOperatorPolicy<T>, but this abstraction is not supported by any of the interesting types you would want to pass asExpression'sT. This is solved by creating an adapter policy and passing that along withT. 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 passintforExpression'sTeven thoughintdoesn't implementIOperatorPolicy<T>itself.Next time I will discuss another application of an adapter policy.
10:27 PM | Add comment | #Programming