2015-06-27 116 views
2

我在运行时遇到了lambda函数的代码引用问题。下面是一个非常简单的例子来说明这一点。我已经给在运行时产生的错误(不编译时)每次尝试下:Code Quotations:如何在内部访问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的一部分。

回答

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 <@[email protected]> (_, _, [a;b]) -> 
      exprFun a b 
    ... 

let q1' = <@ fun x y -> exprStub x y @> 
let q1 = rewrite q1' 
相关问题