可以使用的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.fs和DynamicOperators.fs(我已將鏈接鎖定到修訂版本257)。隨意獲取和使用源代碼爲您自己的目的,它在Apache許可證2.0下授權!或者您可以等待一個星期左右,當我發佈Unquote 2.0.0時,會公開包含評估操作員和擴展。
@Stephen,是的工作..但我仍然想知道它是否會影響我的應用程序的性能我聽說過很多關於'.Eval()'函數及其性能的不好的事情。 – ebb
我在Unquote中使用了'Eval',是的,它並不像它可能的那麼快,但它對於Unquote的工作肯定是足夠的。但對於您的項目,將F#表達式轉換爲數據庫查詢,我會很謹慎。 –
@ebb - 我更新了一個基於反射的eval函數的開始,它應該比PowerPack的'Eval'執行得更好,並且可能已經足夠滿足您的需求。 –