Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expression<Func<T, bool>> from a F# func

Tags:

linq

f#

in linq, .Where takes a Expression> predicate, which I can write in F# as

<@ fun item:'a -> condition @>    // Expr<'a -> bool>

I'm using FSharp.Powerpack to build the expression from a quotation, but what it gives me is a MethodCallExpression. Looking deep, the powerpack code builds the lambda correctly, but wraps it in a Convert call (why is that?). I wonder if casting the argument to the method call (a lambda) would finally give me the Expression> I need.

So the question is why the Convert call, and how to actually get the lambda with the Func signature.

like image 215
hammett Avatar asked Feb 03 '12 19:02

hammett


2 Answers

I can't remember off the top of my head where I found this bit of code, but this is what I use to convert an Expr<'a -> 'b> to Expression<Func<'a, 'b>>. Hopefully this will solve your problem.

open System
open System.Linq.Expressions
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation

let toLinq (expr : Expr<'a -> 'b>) =
  let linq = expr.ToLinqExpression()
  let call = linq :?> MethodCallExpression
  let lambda = call.Arguments.[0] :?> LambdaExpression
  Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters) 
like image 70
Daniel Avatar answered Oct 22 '22 11:10

Daniel


One way you can now do this is to take advantage of the fact that F# will perform this conversion automatically when invoking methods on .NET types that expect a Expression<Func<...>>.

I'm not entirely sure when this got added to the language, but certainly with F# 4, you don't need to explicitly convert F# expressions into LINQ ones. If the reason you wanted to do this in the first place was to be able to use IQueryable LINQ APIs (or other expression-based .NET APIs) then it now just works with no effort, e.g.:

someEfDataContext.MyEntities.Single(fun e -> e.Id = 42)

just works. Even though this looks like an ordinary lambda (we've not used F#'s expression syntax), this compiles to code that produces an F# expression object, and then passes that to LeafExpressionConverter‌​.QuotationToExpressi‌on to turn it into a LINQ expression object.

But sometimes you'll want to get hold of the LINQ-style expression object directly in F#. (E.g., sometimes it's useful to write an F# function that produces an expression that you'll use in multiple queries.) In that case you can write a helper like this:

type FunAs() =
    static member LinqExpression<'T, 'TResult>(e: Expression<Func<'T, 'TResult>>) = e

This looks like it does nothing - it just returns its argument. However, because FunAs is a .NET type, F# will automatically compile any call site that invokes this with a fun expression into code that generates a suitable LINQ query expression. E.g.:

let linqExpr = FunAs.LinqExpression(fun (e:MyEntity) -> e.Id = 42)

Here, linqExpr will be of type Expression<Func<MyEntity, bool>>.

The key to this is that this method is a member of a .NET Type. If you try the exact same thing with an ordinary F# function:

let funAsLinqExpression<'T, 'TResult>(e: Expression<Func<'T, 'TResult>>) = e

which seems like it should mean exactly the same thing as FunAs.LinqExpression, you'll find that you can't call it in the same way. E.g., if you try this:

let linqExpr = funAsLinqExpression(fun (e:MyEntity) -> e.Id = 42)

You'll get a (slightly unhelpful) error: 'This function takes too many arguments, or is used in a context where a function is not expected`.

By making this function a member of a .NET type, we can take advantage of F#'s helpful "You seem to be invoking a .NET API that expects a LINQ-style expression, let me take care of that for you" feature.

(It's possible that there's some more explicit way of asking the LINQ compiler to perform this same trick for you without bringing a .NET type into the picture, but I've not found it.)

like image 39
Ian Griffiths Avatar answered Oct 22 '22 11:10

Ian Griffiths