2011-06-19 53 views
6

我現在正在用引號進行一些非常基本的模式匹配。在報價中評估函數

我的代碼:

let rec test e = 
    match e with 
    | Patterns.Lambda(v,e) -> test e 
    | Patterns.Call(_, mi, [P.Value(value, _); P.Value(value2, _)]) -> 
     printfn "Value1: %A | Value2 : %A" value value2 
    | Patterns.Call(_, mi, [P.Value(value, _); P.PropertyGet(_, pi, exprs)]) -> 
     printfn "Value1: %A | Value2 : %A" value (pi.GetValue(pi, null)) 
    | _ -> failwith "Expression not supported" 


let quot1 = <@ "Name" = "MyName" @> 
(* Call (None, Boolean op_Equality[String](System.String, System.String), 
     [Value ("Name"), Value ("lol")]) *) 

let quot2 = <@ "Name" = getNameById 5 @> 
(* Call (None, Boolean op_Equality[String](System.String, System.String), 
     [Value ("Name"), 
     Call (None, System.String getNameById[Int32](Int32), [Value (5)])]) *) 

test quot1 // Works! 
test quot2 // Fails.. Dosent match any of the patterns. 

是否有可能以某種方式首先評估getNameById函數的結果,所以它將匹配的模式之一,還是我註定要指定一個讓與結果結合的報價之外的功能?

我已經試過與ExprShape模式播放,但沒有運氣..

回答

6

可以使用的PowerPack的Eval只計算參數的Call表達:

match e with 
| Call(_,mi,[arg1;arg2]) -> 
    let arg1Value, arg2Value = arg1.Eval(), arg2.Eval() 
    ... 

,類似的還有Lambda表情等注意到了這個讓您擺脫的Value枚舉排列,Property,以及其他參數表達式。

更新

既然你要避免使用Eval(有很好的理由,如果你正在實施一個性能自覺地運用),你需要使用反射(這仍然沒有減輕,以實現自己的eval函數速度快,但應快於PowerPack的Eval,它涉及F#語句到Linq表達式的中間轉換)。您可以通過支持一組基本表達式開始工作,並根據需要進行擴展。遞歸是關鍵,下面就可以幫助你開始:

open Microsoft.FSharp.Quotations 
open System.Reflection 

let rec eval expr = 
    match expr with 
    | Patterns.Value(value,_) -> value //value 
    | Patterns.PropertyGet(Some(instance), pi, args) -> //instance property get 
     pi.GetValue(eval instance, evalAll args) //notice recursive eval of instance expression and arg expressions 
    | Patterns.PropertyGet(None, pi, args) -> //static property get 
     pi.GetValue(null, evalAll args) 
    | Patterns.Call(Some(instance), mi, args) -> //instance call 
     mi.Invoke(eval instance, evalAll args) 
    | Patterns.Call(None, mi, args) -> //static call 
     mi.Invoke(null, evalAll args) 
    | _ -> failwith "invalid expression" 
and evalAll exprs = 
    exprs |> Seq.map eval |> Seq.toArray 

然後在有源圖案包裝,這將提高語法:

let (|Eval|) expr = 
    eval expr 

match e with 
| Patterns.Call(_, mi, [Eval(arg1Value); Eval(arg2Value)]) -> ... 

更新2

OK,這線程讓我有動力去嘗試和實現一個強大的基於反射的解決方案,並且我已經做到了這一點,並取得了良好的結果,現在是2.0.0版本中的Unquote的一部分。結果並不像我想象的那麼困難,目前我支持所有報價表達式,但Address Address,AddressSet和NewDelegate的除外。這已經比PowerPack的eval更好了,它不支持PropertySet,VarSet,FieldSet,WhileLoop,ForIntegerRangeLoop和Quote。

一些值得注意的實現細節是VarSet和VarGet,我需要將環境名稱/變量查找列表傳遞給每個遞歸調用。它是具有不可變數據結構的函數式編程之美的絕佳例子。

另外值得注意的是特殊照顧與異常相關的問題:當它捕獲來自它正在調用的方法的異常時,通過反射拋出TargetInvokationException異常(這對於正確處理TryWith評估非常重要,並且還可以更好地處理從報價評估中跳出的例外

也許最「困難」的實現細節,或者真正最艱苦的是需要實現所有的核心操作符(嗯,因爲我可以發現的大部分:數字和轉換運算符,檢查版本),因爲它們中的大多數在F#庫中沒有被賦予動態實現(它們使用靜態類型測試來實現而沒有回退動態實現),bu t也意味着在使用這些功能時會顯着提高性能。

一些非正式的基準測試,我觀察到PowerPack(未預編譯)eval的性能增加了50倍。

我還有信心我的基於反射的解決方案比PowerPack的方法更容易出錯,因爲它比PowerPack的方法更簡單(更不用說我已經用大約150次單元測試來支持它,通過Unquotes額外的200多個單元測試,現在由這個評估實現來驅動)。

如果您想要查看源代碼,主要模塊是Evaluation.fsDynamicOperators.fs(我已將鏈接鎖定到修訂版本257)。隨意獲取和使用源代碼爲您自己的目的,它在Apache許可證2.0下授權!或者您可以等待一個星期左右,當我發佈Unquote 2.0.0時,會公開包含評估操作員和擴展。

+0

@Stephen,是的工作..但我仍然想知道它是否會影響我的應用程序的性能我聽說過很多關於'.Eval()'函數及其性能的不好的事情。 – ebb

+0

我在Unquote中使用了'Eval',是的,它並不像它可能的那麼快,但它對於Unquote的工作肯定是足夠的。但對於您的項目,將F#表達式轉換爲數據庫查詢,我會很謹慎。 –

+0

@ebb - 我更新了一個基於反射的eval函數的開始,它應該比PowerPack的'Eval'執行得更好,並且可能已經足夠滿足您的需求。 –

1

你可以寫一個解釋器將評估報價,並調用使用反射getNameById功能。但是,這將是相當多的工作。 ExprShape不會對你有很大的幫助 - 對簡單遍歷引號很有用,但要編寫一個解釋器,你需要覆蓋所有的模式。

我認爲最簡單的方法是評估使用的PowerPack支持語錄:

#r "FSharp.PowerPack.Linq.dll" 

open Microsoft.FSharp.Linq.QuotationEvaluation 

let getNameById n = 
    if n = 5 then "Name" else "Foo" 

let quot1 = <@ "Name" = "MyName" @> 
let quot2 = <@ "Name" = getNameById 5 @> 

quot1.Eval()  
quot2.Eval()  

這有一定的侷限性,但它確實是最簡單的選擇。但是,我不確定你想要達到什麼目標。如果你能澄清這一點,那麼你可能會得到更好的答案。

+0

我正在爲mongodb寫一個簡單的報價翻譯器。例如:你傳遞了'<@ fun z -> z.Name = getNameById 5 @>'它會匹配一些模式,以便使用它自己的查詢函數來構建一個有效的mongo查詢。 '.Eval()'dosent確實有幫助,因爲它評估整個引用,而不僅僅是裏面的'getNameById'函數。 – ebb

+0

@ebb哦,我明白了。你想如何翻譯函數調用? –

+0

'getNameById'函數?呵呵..是啊,我已經想了幾天,現在我仍然是空白的。我唯一的想法是在將引用傳遞給模式匹配之前以某種方式評估函數,但這似乎是不可能的。 – ebb