Yesterday a question from one of the readers prompted me to talk a bit more about the DLR trees, a means DLR uses to represent programs. We focused on just couple of nodes, and today won't be much different, except I'll bring a friend in ... LINQ Expression Trees.
DLR Trees
On the quest to discover what the static DLR tree nodes compile into, we ended up doing a bit of work that compilers generally do for us and hand-crafted a DLR tree to represent function performing an integer addition. You can check out the original source code, today I have pretty much the same thing, module few renames:
using Dlr = Microsoft.Scripting.Ast;
...
public delegate int Adder(int a, int b);
...
// Representing a function performing integer addition in DLR
Dlr.Variable var_a = Dlr.Variable.Parameter(SymbolTable.StringToId("a"), typeof(int));
Dlr.Variable var_b = Dlr.Variable.Parameter(SymbolTable.StringToId("b"), typeof(int));
Dlr.CodeBlock dlr_block = Dlr.Ast.CodeBlock(
"adder", // Function name
typeof(int), // Return type
Dlr.Ast.Return( // Body
Dlr.Ast.Add(
Dlr.Ast.Read(var_a),
Dlr.Ast.Read(var_b)
)
),
new Dlr.Variable[] { var_a, var_b }, // Parameters
new Dlr.Variable[0] // Locals (none needed)
);
// Compile into a delegate
Adder dlr_add = Dlr.TreeCompiler.CompileBlock<Adder>(dlr_block);
// Call the delegate
Console.WriteLine("DLR: 7 + 11 = {0}", dlr_add(7, 11));
Nothing surprising here, just a recap of something we already know. If we run this code, we get the following output:
LINQ Expression Trees
Let's try the same thing with LINQ Expression trees. They have similar object model so the code shouldn't look too different:
using Linq = System.Linq.Expressions;
...
// Representing function performing integer addition in Linq Expression Tree
Linq.ParameterExpression param_a = Linq.Expression.Parameter(typeof(int), "a");
Linq.ParameterExpression param_b = Linq.Expression.Parameter(typeof(int), "b");
Linq.Expression<Adder> linq_lambda_manual = Linq.Expression.Lambda<Adder>(
Linq.Expression.Add( // Lambda value
param_a,
param_b
),
param_a, // params[] of the parameters
param_b
);
// Compile LINQ lambda into a delegate
Adder linq_add = linq_lambda_manual.Compile();
// And call it
Console.WriteLine("LINQ: 3 + 4 = {0}", linq_add(3, 4));
Pretty much the same thing. Create parameters, build the lambda, compile and run. When we run now, we get both DLR and LINQ to answer:
The Best Part
Well, the best part to this all is that C# compiler has direct support in the language for the LINQ expression trees. Using that we get the ultimate version of the same thing:
// C# Compiler support for LINQ
Linq.Expression<Adder> linq_labda = (a, b) => a + b;
Adder super_add = linq_labda.Compile();
Console.WriteLine("C# LINQ: 2 + 3 = {0}", super_add(2, 3));
In fact, the code is now so short it is hard to see the actual tree in there, so let me pull it out:
(a, b) => a + b;
The rest is pure compiler magic of C#. Type inference, detection that the lambda is being assigned to Expression<Adder> and therefore the lambda gets represented as tree (quoted), instead of compiled into a delegate directly. Let's run once more with the compiler supported version of LINQ and we get, not surprisingly:
Conclusion
The unfortunate part is that there is no such support for the DLR trees. The only compilers that know how to produce them, at the moment, are Python, Ruby, ToyScritpt and few others...
In my time working on DLR I wrote many a tree by hand, and it may be just that it is becoming a second nature to me so the typing issue is secondary one. On the other hand, there is a a lot of beauty in other formats, too. Consider this for example:
Next time, I promise to answer the 2nd question Ales posted... can we leverage the DLR caching mechanism in less obvious situations, such as custom formatting support for printing, in general situations where we'd like to avoid switching on types. Until then, happy hacking.