6

I'm using F# 3.0 with .NET 4.5 beta, and I'm trying to convert an F# quotation of type Expr<'a -> 'b> to a LINQ Expression<Func<'a, 'b>>.

I've found several questions that have solutions to this problem, but those techniques don't seem to work any longer, presumably due to changes in either F# 3.0 or .NET 4.5.

In both cases, when I run the code from the solutions of either question, the following action throws an exception:

mc.Arguments.[0] :?> LambdaExpression

...where mc is a MethodCallExpression. The exception is:

System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN' to type 'System.Linq.Expressions.LambdaExpression'.

No, the extra "N" at the end of MethodCallExpressionN is not a typo. Does anyone have a suggestion? Thanks.

UPDATE

Here's a complete reproduction. It turns out this code works fine on an expression like <@ fun x -> x + 1 @>. My problem is that in my case I need to convert an Expr<'a -> 'b> into Expr<'a -> obj> so that I don't have to litter all my lambda expressions with box. I did so by splicing the original expression into this one: <@ %exp >> box @>. This produces an object with the correct type, but the code to convert to Expression<Func<'a, obj>> no longer works.

module Expr =
    open System
    open System.Linq.Expressions
    open Microsoft.FSharp.Quotations
    open Microsoft.FSharp.Linq.QuotationEvaluation

    let rec private translateExpr (linq:Expression) = 
        match linq with
        | :? MethodCallExpression as mc ->
            let le = mc.Arguments.[0] :?> LambdaExpression
            let args, body = translateExpr le.Body
            le.Parameters.[0] :: args, body
        | _ -> [], linq

    let ToFuncExpression (expr:Expr<'a -> 'b>) = 
        let args, body = expr.ToLinqExpression() |> translateExpr 
        Expression.Lambda<Func<'a, 'b>>(body, Array.ofList args) 

let exp = <@ fun x -> x + 1 @>

let r = Expr.ToFuncExpression <@ %exp >> box @>
printfn "%A" r
Community
  • 1
  • 1
Joel Mueller
  • 28,324
  • 9
  • 63
  • 88
  • Perhaps you're being punished for your use of point-free style. What happens if you use `<@ fun x -> %exp x |> box @>` instead? When you use point-free style, the expression you're converting isn't a lambda, it's an application. – kvb May 18 '12 at 17:08
  • @kvb - That's a good thought, but when I use that construct, it underlines `%exp` and tells me "This value is not a function and cannot be applied" and refuses to compile. – Joel Mueller May 18 '12 at 17:12
  • However, `<@ fun x -> x |> %exp |> box @>` does compile. Unfortunately it gets the same error when I try to convert it. – Joel Mueller May 18 '12 at 17:14
  • 1
    `expr.ToLinqExpression()` is now in `F#` as `Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToExpression expr` – Maslow Mar 22 '15 at 15:10

1 Answers1

5

Can you post a more complete sample and also include the F# expression that you're trying to convert?

I tried to test the behaviour on .NET 4.5 using a minimal sample and it worked for me. Here is what I did:

  • I created new F# 3.0 project and copied Linq.fs and Linq.fsi from the 2.0 version of F# PowerPack. (Or is there a 3.0 version of the ToLinqExpression method available somewhere in F# 3.0?)

  • I used the code from Daniel's earlier answer and called the function as follows:

    let r = toLinq <@ fun x -> x + 1 @>
    printfn "%A" r
    

    This did not throw any exception and it printed x => (x + 1), which looks correct to me.

EDIT: To answer the updated question - both of the code samples that you referred to (mine and Daniel's) assume that the body of the quotation is an explicitly constructed function, so they only work on quotations of a specific structure: <@ fun x -> ... @>.

You can fix the problem by using splicing in an explicitly constructed function. The following works for me:

let exp = <@ fun x -> x + 1 @> 
let r = toLinq <@ fun a -> box ((%exp) a) @> 
printfn "%A" r

This contains application of an F# function, so the generated Expression contains a call to ToFSharpFunc (which converts a delegate to an F# function) and then invocation of this. This may be an issue if you want Expression that standard .NET tools can understand (in which case, you'd have to post-process the C# expression tree and remove these constructs).

Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I've updated my question. It turns out it may not have to do with F# 3 or .NET 4.5, but rather the fact that I used quotation splicing. And yes, I copied the powerpack Linq files into my project just like you did. – Joel Mueller May 18 '12 at 16:33
  • @JoelMueller Thanks - yes, the problem is that you don't pass it quotation containing explicit lambda. See my modified answer. – Tomas Petricek May 18 '12 at 17:13
  • The `translateExpr` function was still throwing a (different) error with your updated answer, so I switched it to Daniel's code, and now it works. Thanks! – Joel Mueller May 18 '12 at 17:26
  • Unfortunately, AutoMapper (which I'm ultimately trying to integrate with) can't seem to handle any LINQ expressions more complicated than a property access. And, it won't accept `Expression>` in place of `Expression>`. So I can't box or cast... I need to make a new question for this. – Joel Mueller May 18 '12 at 18:11