Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I reuse an Expression when building a more complex one?

I'm trying to learn Expressions, mainly for my own education. I am trying to work out how to build up an Expression that would represent something more complex than things like a+b and so on.

I'll take this step by step, so you can see how I'm building it up. Please feel free to comment on any aspect of my approach, although the actual question comes on the third code block.

I understand how to make a function that divides the input by 2:

// Set up a parameter for use below
ParameterExpression x = Expression.Parameter(typeof(double), "x");

Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
// Test it
double halfOfTwenty = Expression.Lambda<Func<double, double>>(halve, x).Compile()(20);

I have also worked out how to make an expression that computes sin(x):

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);
// Test it
double sinePiOverTwo = Expression.Lambda<Func<double, double>>(sine, x).Compile()(Math.PI / 2);

What I would like to do now is create an expression that computes sin(x/2). I can do this like this...

Expression sineOfHalf = Expression.Call(typeof(Math).GetMethod("Sin"), halve);

...but ideally I would like to reuse my existing sine expression, rather than creating a new one.

I'm sure this is straightforward, but to a newbie in this area, I'm finding it quite difficult. Anyone able to show me how to do this? I've looked through the Expression class, but have obviously overlooked the method that I need.

like image 264
Avrohom Yisroel Avatar asked Dec 21 '17 15:12

Avrohom Yisroel


1 Answers

ParameterExpression x = Expression.Parameter(typeof(double), "x");

Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);

Okay, at this point you have something that represents x / 2 and your next step created the lambda expression x => x / 2. (Incidentally, you could also have used Expression.Divide() rather than MakeBinary to be a bit more concise.

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);

At this point you have an expression that represents Math.Sin(x) and your next step created the lambda expression x => Math.Sin(x).

So what you need to do is to combine the two points you were at before you created a lambda expression each time:

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), halve);

And now you can do the final step on that:

Expression.Lambda<Func<double, double>>(sine, x) // x => Math.Sin(x / 2.0)

Entire code:

ParameterExpression x = Expression.Parameter(typeof(double), "x");
Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), halve);
Expression<Func<double, double>> sineHalveLambda = Expression.Lambda<Func<double, double>>(sine, x);

And to test:

Func<double, double> f = sineHalveLambda.Compile();
Console.WriteLine(f(Math.PI));  // 1
Console.WriteLine(f(0));        // 0
Console.WriteLine(f(-Math.PI)); // -1

Incidentally, it can often be useful to have using static System.Linq.Expressions.Expression; in your file when working directly with the Expression class, since you'll use its static members so often, and then it can sometimes help visualise the tree produced if you do it as a one-liner but with indenting reflecting the tree:

ParameterExpression x = Parameter(typeof(double), "x");
Expression<Func<double, double>> sineHalveLambda = Lambda<Func<double, double>>(
    Call(
        typeof(Math).GetMethod("Sin"),
        Divide(
            x,
            Constant(2.0)
        )
    )
    , x);

Because then the indentation reflects the branches of the expression tree produced. There's a balance here though between the readability advantage in reflecting the tree structure, and the general readability disadvantage of one-liners generally.

Edit: As @Evk points out, I missed the bit in your question that said "I can do this like this…" which is much as the above.

To actually reuse the sine Expression there are a few possible approaches.

You can use Update which produces an Expression based on the Expression you are working with, with different children. This is heavily used in ExpressionVisitors.

You can also create a lambda expression and invoke that lambda in another expression:

ParameterExpression x = Expression.Parameter(typeof(double), "x");
Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);
Expression sineLambda = Expression.Lambda<Func<double, double>>(sine, x);
Expression<Func<double, double>> sineHalfLambda = Expression.Lambda<Func<double, double>>(Expression.Invoke(sineLambda, halve), x);
Func<double, double> sineHalfDelegate = sineHalfLambda.Compile();

Here what you are actually producing isn't x => Math.Sin(x/2) but rather first a sine that is x => Math.Sin(x) and then a second expression that is x => sine(x / 2).

Conceptually this means you've got two compiled lambda expressions, but the compiler is able to inline the inner lambda so that what is actually being compiled is back to being x => Math.Sin(x/2) again, so you don't have the overhead of two separate compilations.

More generally though, it pays to think about what your units of reuse really are. If I wanted to produce several expressions that called Math.Sin on different expressions' results, I'd probably hold on to the MethodInfo that typeof(Math).GetMethod("Sin") returns and use that as my reusable component.

like image 67
Jon Hanna Avatar answered Sep 30 '22 15:09

Jon Hanna