我在运行时遇到了为lambda函数编写代码报价的麻烦。下面是一个高度简化的例子来说明这一点。我已经在每次尝试的下面给出了在运行时(不是编译时)产生的错误:
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
我完全清楚这个例子并不要求x & y variables
传递给exprFun
但是在我的实际例子中我需要这种行为,因为我将这些变量传递给复杂的递归将返回Code Quotation / Expression本身的函数。
实际上,我的要求是exprFun
能够访问/操作这些变量,作为生成lambda函数的rhs的Code Quotation的一部分。
答案 0 :(得分:3)
如果你考虑一下,关于“逃避范围”的错误完全有意义:如果你“记住”这些变量,然后将它们插入一个没有意义的上下文(即超出其范围),该怎么办?编译器不能保证这种方式的正确性。你不应该被允许以这种方式使用这些变量。
您可以做的是让exprFun
管理自己的变量并返回Expr<int-> int-> int>
而不只是Expr<int>
:
let exprFun = <@ fun a b -> a + b @>
let q1 = <@ fun x y -> (%exprFun) x y @>
当然,结果表达式并不完全等同于您期望获得的表达式。也就是说,而不是:
fun x y -> x + y
你会得到这个:
fun x y -> (fun a b -> a + b) x y
但这相当于逻辑,因此对任何体面的报价消费者都不应该是一个问题。
或者,如果您真的坚持拼接使用参数动态生成的引用,则可以使用存根函数调用,然后将引用重写为单独的步骤:
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'