2010-08-05 108 views
6

比方說,我們有一個簡單的F#報價:生成參數F#報價

 
type Pet = { Name : string } 
let exprNonGeneric = <@@ System.Func(fun (x : Pet) -> x.Name) @@> 

得到的報價是這樣的:

 
val exprNonGeneri : Expr = 
    NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], 
      x, PropertyGet (Some (x), System.String Name, [])) 

現在我想概括它,所以我代替型「寵物「和屬性」名稱「我可以使用任何類型和方法/屬性定義它。這裏是我想要做的事:

 
let exprGeneric<'T, 'R> f = <@@ System.Func<'T, 'R>(%f) @@> 
let exprSpecialized = exprGeneric<Pet, string> <@ (fun (x : Pet) -> x.Name) @> 

所得的表達是現在不同了:

 
val exprSpecialized : Expr = 
    NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], 
      delegateArg, 
      Application (Lambda (x, 
            PropertyGet (Some (x), System.String Name, [])), 
          delegateArg)) 

正如你所看到的,第一和第二個表達式之間的區別是,在第一種情況下頂級NewDelegate表達式包含PropertyGet,而第二個表達式將PropertyGet包裝在Application/Lambda表達式中。當我將這個表達式傳遞給外部代碼時,它並不期望這樣的表達式結構並失敗。所以我需要一些方法來構建一個通用版本的引用,所以當它被專門化時,得到的引用是完全匹配的< @@ System.Func(fun(x:Pet) - > x.Name) @@ >。這可能嗎?或者只有選擇手動將模式匹配應用到生成的報價並將其轉換爲我需要的選項?

UPDATE。作爲一種變通方法,我實現了以下接口:

 
let convertExpr (expr : Expr) = 
    match expr with 
    | NewDelegate(t, darg, appl) -> 
     match (darg, appl) with 
     | (delegateArg, appl) -> 
      match appl with 
      | Application(l, ldarg) -> 
       match (l, ldarg) with 
       | (Lambda(x, f), delegateArg) -> 
        Expr.NewDelegate(t, [x], f) 
       | _ -> expr 
      | _ -> expr 
    | _ -> expr 

它的工作 - 我現在可以從第1轉換表達形式第二。但我有興趣查明這是否可以以簡單的方式實現,而不需要遍歷表達式樹。

回答

6

我不認爲這是可能的,在第二種情況下,您將用Lambda節點表示的表達式<@ (fun (x : Pet) -> x.Name) @>插入另一個表達式的孔中。編譯器在插入過程中不會簡化表達式,因此無論您做什麼,Lambda節點都不會被刪除。

但是你的模式匹配的解決方法可以大大簡化:

let convertExpr = function 
| NewDelegate(t, [darg], Application(Lambda(x,f), Var(arg))) 
    when darg = arg -> Expr.NewDelegate(t, [x], f) 
| expr -> expr 

事實上,你更復雜的版本不正確。這是因爲最內層模式中的delegateArg與外層模式中先前綁定的delegateArg標識符的值不匹配;它是一個新的,剛剛結合的標識符,它也恰好被稱爲delegateArg。實際上,外部delegateArg標識符的類型爲Var list,而內部標識符的類型爲Expr!但是,由於編譯器生成的表達式範圍有限,您的破解版本在實踐中可能不會有問題。

編輯

關於你的後續問題,如果我理解正確,它可能無法達到你想要什麼。與C#不同,其中x => x + 1可被解釋爲具有Func<int,int>Expression<Func<int,int>>的類型,在F#fun x -> x + 1中始終爲int->int類型。如果您想獲得Expr<int->int>類型的值,則通常需要使用報價運算符(<@ @>)

但是,有一種替代方案可能有用。您可以使用允許綁定函數的[<ReflectedDefinition>]屬性使其報價也可用。這裏有一個例子:

open Microsoft.FSharp.Quotations 
open Microsoft.FSharp.Quotations.ExprShape 
open Microsoft.FSharp.Quotations.Patterns 
open Microsoft.FSharp.Quotations.DerivedPatterns 

let rec exprMap (|P|_|) = function 
| P(e) -> e 
| ShapeVar(v) -> Expr.Var v 
| ShapeLambda(v,e) -> Expr.Lambda(v, exprMap (|P|_|) e) 
| ShapeCombination(o,l) -> RebuildShapeCombination(o, l |> List.map (exprMap (|P|_|))) 


let replaceDefn = function 
| Call(None,MethodWithReflectedDefinition(e),args) 
    -> Some(Expr.Applications(e, [args])) 
| _ -> None 


(* plugs all definitions into an expression *) 
let plugDefs e = exprMap replaceDefn e 

[<ReflectedDefinition>] 
let f x = x + 1 

(* inlines f into the quotation since it uses the [<ReflectedDefinition>] attribute *) 
let example = plugDefs <@ fun y z -> (f y) - (f 2) @> 
+0

謝謝你的回答和一個很好的建議。然而,我仍然堅持相關的問題:是否有可能將簡單的F#委託(例如fun x - > x.Name)插入到與實際類型無關的通用引用中,如上面的引用#2。這與模擬框架的作用類似:它們定義一些表達式而不知道具體的接口並注入具體的類型。我似乎無法在F#中實現這一點。 – 2010-08-05 17:41:51

+0

@Vagif - 我不知道我理解你的問題。你能詳細談談你想做什麼嗎? – kvb 2010-08-05 18:51:31

+0

我需要在F#和C#之間發送interop。 C#預計某種類型的LINQ表達式。我可以用硬編碼的方式在F#中編寫它,但我想爲此構建一個通用的F#包裝器。這個包裝器應該能夠像「fun x - > x.Name」一樣輸入lambdas並將它們轉換爲引號。我開始懷疑在一般情況下這是不可能的。 – 2010-08-05 19:35:35