我现在正在做一些非常基本的模式匹配报价.
我的代码:
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.
Run Code Online (Sandbox Code Playgroud)
是否有可能以某种方式首先评估getNameById函数的结果,以便它匹配其中一个模式,或者我注定要在引用之外分配一个let绑定函数的结果?
我试过玩ExprShape模式,但没有运气..
您可以使用PowerPack Eval仅评估Call表达式的参数:
match e with
| Call(_,mi,[arg1;arg2]) ->
let arg1Value, arg2Value = arg1.Eval(), arg2.Eval()
...
Run Code Online (Sandbox Code Playgroud)
同样地,对于Lambda表情等注意到了这个让您摆脱的枚举排列Value,Property以及其他参数表达式.
更新
由于你想避免使用Eval(出于好的理由,如果你正在实现一个注重性能的应用程序),你需要使用反射来实现你自己的eval函数(它仍然没有快速闪亮,但应该比Eval涉及中间体的PowerPack更快将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
Run Code Online (Sandbox Code Playgroud)
然后将其包装在活动模式中将改善语法:
let (|Eval|) expr =
eval expr
match e with
| Patterns.Call(_, mi, [Eval(arg1Value); Eval(arg2Value)]) -> ...
Run Code Online (Sandbox Code Playgroud)
更新2
好的,这个帖子让我有动力去尝试实现一个强大的基于反射的解决方案,而且我已经做了很好的结果,现在是版本2.0.0 的Unquote的一部分.
事实证明并不像我想象的那么困难,目前我支持除 AddressGet,AddressSet和NewDelegate 之外的所有引用表达式.这已经比PowerPack的eval更好了,它不支持PropertySet,VarSet,FieldSet,WhileLoop,ForIntegerRangeLoop和Quote.
一些值得注意的实现细节是使用VarSet和VarGet,我需要在每个递归调用中传递一个环境名称/变量查找列表.它实际上是使用不可变数据结构进行函数式编程之美的一个很好的例子.
另外值得注意的是对异常问题的特别关注:当它捕获来自它正在调用的方法的异常时,对反射抛出的TargetInvokationExceptions进行条带化处理(这对于正确处理TryWith评估非常重要,并且还可以更好地处理飞出的异常的用户报价评估.
也许最"困难"的实现细节,或者实际上最艰苦的是需要实现所有核心运算符(好吧,正如我发现的大多数:数字和转换运算符,也检查版本),因为大多数都是没有在F#库中给出动态实现(它们是使用静态类型测试实现的,没有回退动态实现),但也意味着在使用这些函数时性能会有很大提高.
我观察到一些非正式基准测试,与PowerPack(未预编译)评估相比,性能提高了50倍.
我也相信我的基于反射的解决方案不会像PowerPack那样容易出错,只是因为它没有PowerPack的方法那么复杂(更不用说我用大约150个单元测试来支持它,并且通过Unquotes附加200强化了它+单元测试现在由此eval实现驱动).
如果你想查看源代码,主要模块是Evaluation.fs和DynamicOperators.fs(我已将链接锁定到修订版257).您可以根据自己的需要获取和使用源代码,它可以在Apache License 2.0下获得许可!或者你可以等一个星期左右,当我发布Unquote 2.0.0时,它将公开包括评估运营商和扩展.