C# Expressions vs. F# Quotations: A Syntax Comparison

In C# the easiest and most common way to create an expression is by implicitly converting a single line lambda into an expression:

Expression<Func<int, int>> expression = x => x * 10;

This implicit conversion is great for some APIs, like the mocking library MOQ:

[Test]
public void MoqSample()
{
    var mock = new Mock<IList<int>>();
    mock.SetupGet(l => l.Count).Returns(15);
    Assert.AreEqual(15, mock.Object.Count);
}

When I use MOQ, I’m glad that the l => l.Count lambda automatically gets converted to an expression, but there are pitfalls that you need to be wary of with this conversion.

It’s often good practice to refractor complicated lambdas into helper methods for reuse and code clarity:

//Using a simple lambda predicate
var values =
    0.Through(20)
    .Where(x => x > 15);
//refactoring the predicate into a helper method
//NOTE: in practice, I'd probably leave this as a lambda
//since it's neither complicated nor multi-purpose.
public bool Condition(int x)
{
    return x > 15;
}

var values =
    0.Through(20)
    .Where(Condition);

However, you need to be careful when using this technique with some APIs. For example, the following example uses LINQ to SQL to translate a Where predicate into a SQL call:

using (var context = new SampleDataContext(""))
{
    context.Foos.Where(x => x.Bar == "a match");
}

If you refactor this predicate into a helper method, the result is not what you may expect:

public bool Condition(Foo x)
{
    return x.Bar == "a match";
}

using (var context = new SampleDataContext(""))
{
    context.Foos.Where(Condition);
}

The helper method is not implicitly converted into a predicate like the lambda version, but the code still compiles. Instead, the Where method from LINQ to Objects is called. The method will still return the same result, so your integration tests won’t fail, but the behavior has changed. Instead of translating the Where predicate into SQL, the query will pull all of the Foo objects from the database and filter in memory using LINQ to Objects. This can have a huge impact on performance, and only load testing or QA testing this particular part of the application will catch it. Ouch.

The F# version of expressions is quotations, but F# doesn’t implicitly cast lambdas into quotations. Instead, you have to explicitly wrap code using a special notation:

let quotation = <@fun x -> x = "a match"@>

Honestly, I think that this syntax is ugly, but it means that you can’t accidently call the wrong version of an overloaded method like you can in C#. If you translate the LINQ to SQL example from above into F#, it looks like this:

//NOTE: The following won't compile since LINQ to SQL 
//      doesn't support Where with a quotation
use context = new SampleDataContext("")
let values = context.Foos.Where(<@fun x -> x.Bar = "a match"@>)

Here, the conversion of the predicate into a quotation is explicit. Unfortunately, you can’t have your cake and eat it too, so an F# version of MOQ would also require you to tag your lambdas even when there is no danger of calling the wrong overloaded function:

//NOTE: The following won't compile since MOQ doesn't
//      support SetupGet with a quotation
[Test]
let MoqSample() =
    let mock = new Mock<IList<int>>()
    mock.SetupGet(<@fun l -> l.Count@>).Returns(15)
    Assert.AreEqual(15, mock.Object.Count)

Ultimately, choosing between implicit and explicit casting is a game of tradeoffs. Until I encountered the overloading problem with C#’s implicit style, I found F#’s explicit approach to be ugly and unnecessary. Now that I better understand some problems of implicit casting to expressions/quotations, I appreciate the explicit approach- even if I’m still not a big fan of the characters used to make the tags.

This entry was posted in C#, F#. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*
*