Franco Tiveron
Franco Tiveron

Reputation: 2928

Composing F# code quotations programmatically

The following code is extracted from an application and adapted to highlight the issue as easy as possible

module Mo

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

type Type() = 
    member _.Prop1 with get() = 1
    member _.Prop2 with get() = 2

let toFunc<'t when 't :> Type>(filter: 't -> Expr<bool>) =
    let xp = <@ Func<'t, bool>(fun (t: 't) -> %(filter t) && t.Prop2 = 2) @>
    LeafExpressionConverter.QuotationToExpression xp |> unbox<Expression<Func<'t, bool>>>

let getFunc (i: int) = 
    let filter (t: Type) = <@ t.Prop1 = i @>
    toFunc filter

the problem is in the line

let xp = <@ Func< 't, bool>(fun (t: 't) -> %(filter t) && t.Prop2 = 2) @>

The compiler complains in fun (t: 't) as follows:

Error   FS0446  
The variable 't' is bound in a quotation but is used as part of a spliced expression.
This is not permitted since it may escape its scope.

The intent is to compose quotations into a filter Linq expression. Is there a (alternative) way to do this?

EDIT:

Some more context looks necessary:

The returned Func expression is later passed to Azure.Data.Tables.TableClient.Query(). Unfortunately this method doesn't support expressions containing function calls.

Converting filter to a quoted function (as suggested by Fyodor) was my first version, but I had to abandon it because of this requirement of the Azure Tables SDK.

So the question becomes :

Is it possible to achieve the result of an expression that doesn't contain calls to external method/function?

Upvotes: 1

Views: 331

Answers (1)

Fyodor Soikin
Fyodor Soikin

Reputation: 80915

You're mixing up your variables between quotation realm and "regular" realm.

filter is not a quoted function. It's a regular function that returns a quotation. Regular functions get regular parameters, quoted functions get quoted parameters.

The t parameter here:

let xp = <@ Func<'t, bool>(fun (t: 't) -> %(filter t) && t.Prop2 = 2) @>
                                ^
                                |
                             This one

That's a quoted parameter. It's defined inside a quotation.

And yet, you're trying to pass its value to the filter function. But at the moment of constructing the quotation, the parameter t doesn't have a value yet! Only when you're done constructing the quotation, then compile it to IL, and then call it, - only then will the parameter t have a value, allowing you to call the filter function. But you need the result of filter function to finish constructing the quotation in the first place!

The most straightforward fix is to make filter a quoted function. Then you can splice it into the quotation, and then pass the parameter t to the result of splicing:

let toFunc<'t when 't :> Type>(filter: Expr<'t -> bool>) =
    let xp = <@ Func<'t, bool>(fun (t: 't) -> (%filter) t && t.Prop2 = 2) @>
    LeafExpressionConverter.QuotationToExpression xp |> unbox<Expression<Func<'t, bool>>>

let getFunc (i: int) = 
    let filter = <@ fun (t: Type) -> t.Prop1 = i @>
    toFunc filter

Upvotes: 2

Related Questions