问题
I am having trouble at runtime in putting together a code quotation for a lambda function. Below is a highly simplified example to demonstrate the point. I have given the errors yielded at runtime (not compile time) underneath each attempt:
open FSharp.Quotations
// First Attempt
let exprFun (a:int) (b:int) :Expr<int> = <@ a+b @>
let q1:Expr<int->int->int> = <@ fun x y -> %(exprFun x y) @> // NB: need to pass around and access `x` & `y` within a nested quotation expression
// error: The variable 'x' is bound in a quotation but is used as part of a spliced expression. This is not permitted since it may escape its scope.
// Second Attempt
let x = new Var("x", typeof<int>)
let xe = Expr.Cast<int> ( Expr.Var(x) )
let y = new Var("y", typeof<int>)
let ye = Expr.Cast<int> ( Expr.Var(y) )
let q2 = Expr.Cast< int->int->int > ( Expr.Lambda(x, Expr.Lambda(y, <@ %(exprFun %xe %ye) @> )) )
// System.InvalidOperationException: first class uses of '%' or '%%' are not permitted
I am fully aware that this example doesn't require that the x & y variables
be passed to exprFun
but in my real-world example I need this behaviour as I am passing these variables into a complex recursive function that will return a Code Quotation/Expression itself.
Effectively, my requirement is that exprFun
be able to access/manipulate these variables as part of the generation of the Code Quotation for the rhs of the lambda function being generated.
回答1:
If you think about it, the error about "escaping scope" totally makes sense: what if you "remember" these variables and then insert them in a context that doesn't make sense (i.e. out of their scope)? The compiler can't guarantee correctness that way. You shouldn't really be allowed to work with these variables that way.
What you can do instead is make exprFun
manage its own variables and return Expr<int-> int-> int>
instead of just Expr<int>
:
let exprFun = <@ fun a b -> a + b @>
let q1 = <@ fun x y -> (%exprFun) x y @>
Of course, the resulting expression won't be exactly equivalent to what you're expecting to get. That is, instead of this:
fun x y -> x + y
You will get this:
fun x y -> (fun a b -> a + b) x y
But this is equivalent logically, and so shouldn't be a problem for any decent quotation consumer.
Alternatively, if you really insist on splicing a quotation dynamically generated using arguments, you can use a stub function call and then rewrite the quotation as a separate step:
let exprStub (a: int) (b: int): int = failWith "Don't call me!"
let exprFun (a:Expr) (b:Expr) -> <@@ %%a + %%b @@>
let rec rewrite (e: Expr<_>): Expr<_> =
match e with
...
| SpecificCall <@exprStub@> (_, _, [a;b]) ->
exprFun a b
...
let q1' = <@ fun x y -> exprStub x y @>
let q1 = rewrite q1'
来源:https://stackoverflow.com/questions/31093528/code-quotations-how-to-access-variables-of-a-lambda-function-internally