One way to view andand is as a syntactic transformation of the code: the effect we want is to write
a.b.c.d
and have that automatically translated to
(a != null && b != null && c != null) ? a.b.c.d : null
C# doesn't support syntactic abstraction in the style of Lisp macros, but later in this post we'll see that recent versions of C# give us much more flexibility than we had in 2.0 and earlier.
Before we look at the techniques in detail, I want to acknowledge that syntactic regularity is a key motivation for Ruby Object#andand. Ruby Object#andand uses infix "andand" in imitation of the built-in guarded assignment operator &&=. We have no such operator in C#, and I am a prefix sympathizer anyway, so neither of my andand implementations share this emphasis with the Ruby inspiration.
So, my first implementation of andand for C# used LINQ expression tree transformation. The first step is to gain the ability to manipulate the program defined by the source code whose meaning I want to reinterpret (e.g., the compound member access expression a.b.c.d shown above):
public static T Andand<T>(Expression<Func<T>> f) {
try {
f = (Expression<Func<T>>)aar(f);
return f.Compile()();
}
catch (Exception e) {
throw new Exception(String.Format("trouble with {0}", f), e);
}
}
One of the main ideas in LINQ is type-directed expression quoting. Here, I've written a function that takes an Expression tree as an argument. If I apply this function to a lambda expression, the compiler will translate the lambda to a value representing the abstract syntax tree of the lambda rather than generating an anonymous delegate. So, a typical call site for Andand looks like this:
private class D {}
private class C {
public D d;
}
private class B {
public C c;
}
private class A {
public B b;
}
A ab = new A();
ab.b = new B();
Assert.AreEqual(ab.b, Andand(() => ab.b));
Assert.IsNull(Andand(() => ab.b.c.d));
Once I have my expression tree, I'm ready for a transformation. Clearly from my starting example, member access expressions constitute an important case. I'd like to be able to handle some other cases though, like method chaining. I'll give you a taste of tree inspection and construction here; for complete details see andand-sans-monads at googlecode.
private static Expression aar(Expression e) {
var me = e as MemberExpression;
if (me != null) {
return
guard(me.Expression, me);
}
var l = e as LambdaExpression;
if (l != null) {
return
Expression.Lambda(
guard(
l.Body,
l.Body),
Enumerable.Select(
l.Parameters,
p => p).ToArray());
}
var ie = e as InvocationExpression;
if (ie != null) {
var args =
Enumerable.Select(
ie.Arguments,
(arg, i) => Expression.Parameter(arg.Type, String.Format("arg{0}", i))).ToArray();
return
Expression.Invoke(
guard(
ie.Expression,
Expression.Lambda(
nullExpr(ie.Expression.Type),
args),
Expression.Lambda(
ie.Expression,
args)),
Enumerable.Select(
ie.Arguments,
arg => aar(arg)));
}
return e;
}
It sure is a lot of work to construct expression trees "by hand" as opposed to using the type-directed quoting facility. I could be a lot more concise with Lisp quasiquote. Maybe later we'll experiment with using type-directed quotation of expression tree transformations, but for now I was more interested in exploring the connection between andand and monads.
But I have to work tomorrow morning and I'd like you to be able to read this now, so I'll save the monad and GADT discussion for my next post in this series.
No comments:
Post a Comment