2015-06-27 80 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' 
相關問題