评估报价内的功能

时间:2011-06-19 16:23:58

标签: f#

我正在做一些非常基本的模式匹配报价。

我的代码:

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函数的结果,以便它匹配其中一个模式,或者我注定要在引号外指定一个let绑定函数的结果?

我尝试使用ExprShape模式,但没有运气..

2 个答案:

答案 0 :(得分:7)

您可以使用PowerPack的Eval仅评估Call表达式的参数:

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

同样适用于Lambda表达式等。注意这可以使您免于枚举ValueProperty和其他参数表达式的排列。

<强>更新

由于你想避免使用Eval(出于好的理由,如果你正在实现一个注重性能的应用程序),你需要使用反射来实现你自己的eval函数(它仍然没有快速闪亮,但应该比PowerPack的Eval更快,它涉及到F#Quotations到Linq Expressions的中间翻译。您可以通过支持一组基本表达式开始,并根据需要从那里进行扩展。递归是关键,以下内容可以帮助您入门:

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

好的,这个帖子让我有动力去尝试实现一个强大的基于反射的解决方案,而且我已经做了很好的结果,现在是版本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个单元测试来支持它,正确强化了取消引用额外的200多个单元测试,现在由此eval实现驱动。

如果您想查看源代码,主要模块是Evaluation.fsDynamicOperators.fs(我已将链接锁定到修订版257)。您可以根据自己的需要获取和使用源代码,它可以在Apache License 2.0下获得许可!或者你可以等一个星期左右,当我发布Unquote 2.0.0时,它将公开包括评估运营商和扩展。

答案 1 :(得分:1)

您可以编写一个解释器来评估报价并使用Reflection调用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()    

这有一些限制,但它确实是最简单的选择。但是,我不确定你想要实现什么目标。如果你能澄清一下,那么你可能会得到一个更好的答案。