f# - 评估引号内的函数

标签 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 绑定(bind)与函数的结果?

我试过玩ExprShape模式,但没有运气..

最佳答案

您可以使用 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

好的,这个线程让我有动力尝试实现一个基于反射的强大解决方案,我已经这样做了,结果很好,现在是 Unquote 的一部分从 2.0.0 版开始。

结果并没有我想象的那么难,目前我支持除 AddressGet、AddressSet 和 NewDelegate 之外的所有引用表达式。这已经比 PowerPack 的 eval 好,后者不支持 PropertySet、VarSet、FieldSet、WhileLoop、ForIntegerRangeLoop 和 Quote。

一些值得注意的实现细节是 VarSet 和 VarGet,我需要将环境名称/变量查找列表传递给每个递归调用。它确实是具有不可变数据结构的函数式编程之美的一个很好的例子。

同样值得注意的是对异常问题的特别注意:当反射捕获来自它正在调用的方法的异常时,将反射抛出的 TargetInvokationExceptions strip 化(这对于正确处理 TryWith 评估非常重要,并且还有助于用户更好地处理飞出的异常的报价评估。

也许最“困难”的实现细节,或者说真的是最累人的,是需要实现所有的核心操作符(好吧,我发现的大多数操作符:数字和转换操作符,以及检查版本),因为它们中的大多数都是在 F# 库中没有给出动态实现(它们是使用静态类型测试实现的,没有回退动态实现),但也意味着使用这些函数时性能会大大提高。

一些非正式的基准测试我观察到性能比 PowerPack 的(未预编译的)评估提高了 50 倍。

我也相信我的基于反射的解决方案比 PowerPack 的解决方案更不容易出错,仅仅是因为它比 PowerPack 的方法更简单(更不用说我已经用大约 150 个单元测试来支持它,并通过 Unquotes 额外的 200 + 现在由这个 eval 实现驱动的单元测试)。

如果你想偷看源代码,主要模块是Evaluation.fsDynamicOperators.fs (我已将链接锁定到修订版 257)。随意获取和使用源代码用于您自己的目的,它在 Apache 许可证 2.0 下获得许可!或者您可以等待一周左右,当我发布 Unquote 2.0.0 时,它将公开包括评估运算符和扩展。

关于f# - 评估引号内的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6403600/

相关文章:

f# - 观察模式下的 Suave(开发期间)

f# - F# 是否有异步验证库?

F# 在运行时获取类型参数的大小

f# - Fsharp.Data XmlProvider 对 DateTime 的类型推断不正确

.net - F# 环境集成(用于脚本)

macos - 在 OSX 10.8 上编程 F# 的最佳方式是什么?

f# - FsLex 因 '{' 上的解析错误而中止

javascript - 可以使用 websharper 作为 JS 的替代品吗?

f# - 如何使用 Fable (F#) 进行 base64 编码?

.net - 当 Generic.List<T>.Add 是函数中的最后一个语句并且尾调用优化开启时的性能损失