在 F# 中输入通用鸭子?
Posted
技术标签:
【中文标题】在 F# 中输入通用鸭子?【英文标题】:generic duck typing in F#? 【发布时间】:2011-05-25 07:36:04 【问题描述】:使用 let 内联和成员约束,我将能够为已知成员进行鸭式输入,但如果我想定义一个像这样的通用函数怎么办:
让duckwrapper duck = ...
带有签名 'b -> 'a 并且返回值将是一个实现 'a 的对象(这将是一个接口)并将调用转发给鸭子。
我已经在 C# 中使用 Reflection.Emit 完成了这项工作,但我想知道 F# 反射、引用或其他构造是否会更容易。
关于如何完成此任务的任何建议?
编辑 阅读蒂姆斯的回答后,我想我会提供更多细节
当我写到使用引用来帮助时,我的想法是这样的:
new IInterface with member x.SayHello() = !!<@ %expr @>
!!是将引用转换为函数的运算符,%expr 是该方法的工作单元。我可以将表达式转换为函数(我猜)但不知道如何
当然,这也不能完全做到这一点,因为 IInterface 将是 'a,我希望 F# 反射可能有一些方便的函数,以便我可以基于类型对象和一些函数值构造一个类型
编辑 作为 Tomas Petricek 答案的更新,我将提供一些代码来解释我的需求
type SourceRole =
abstract transfer : decimal -> context
and context(sourceAccount:account, destinationAccount) =
let source = sourceAccount
let destination = destinationAccount
member self.transfer amount =
let sourcePlayer =
new SourceRole with
member this.transfer amount =
use scope = new TransactionScope()
let source = source.decreaseBalance amount
let destination = destination.increaseBalance amount
scope.Complete()
context(source,destination)
sourcePlayer.transfer(amount)
这是在 F# 中移植 DCI 的“教科书”示例的尝试。源和目标是 DCI 角色。这个想法是任何遵守特定合同的数据对象都可以播放这些。在这种情况下,合同很简单。 source 需要一个名为 reduceBalance 的成员函数,destination 需要一个名为 increaseBalance 的成员函数。 对于这种特定情况,我可以使用 let inline 和 member 约束来实现这一点。 但是我想写一组给定接口和对象的函数。在这种情况下,它可能是源(作为对象)和
type sourceContract =
abstract decreaseBalance : decimal -> sourceContract
作为类型。结果将是一个 sourceContract 类型的对象,它将方法调用通过管道传递给源对象上具有相同名称的方法。
【问题讨论】:
查看我的编辑以响应您的编辑。 F# 反射无法生成代码。 F# 引用不足以满足您的需求。 Reflection.Emit 在这里使用是正确的。 F# 使用结构类型,而不是鸭子类型。它们非常相似,因为它们都使用类型推断,但是结构类型是静态的,而鸭子类型是动态的(还有其他区别)。听起来你想做的事情可以通过 OCaml 中的仿函数来完成,但我不确定 F# 的等价物是什么。 您也可以为此使用 .NET Remoting。 @Brian Remoting 代理是另一种实现鸭子类型的方法。但是,这种方法比在内存中生成包装类要慢得多。 @Niki 我的任务(以及因此的问题)与使鸭子打字成为可能有关。结构类型要求两种类型的结构相同 鸭类型要求在运行时使用的部分相同。我只关心运行时使用的部分,也就是鸭子打字 【参考方案1】:您可以使用来自F# PowerPack 的组件编译 F# 引用。所以我认为你可以使用引号在运行时生成和执行代码。如果你写一个代表函数的引用并编译它,你会得到一个函数值,你可以用它来实现一个接口。这是一个简单的例子:
#r "FSharp.PowerPack.Linq.dll"
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation
// Create a part using "Expr." calls explicitly
let expr = Expr.Value(13)
// Create a part using quotation syntax
let expr2 = <@ (fun x -> x * %%expr) @>
// Compile & Run
let f = expr2.Compile()()
f 10
您可以混合引用语法和对Expr
的调用,这样可以更轻松地从基本块编写代码。编译有点愚蠢(目前),因此生成的代码不会像通常的 F# 代码那样高效(但您需要根据自己的情况对其进行测量)。
我不太确定我明白你到底想做什么,所以如果你能提供更多细节,我可以给出更具体的答案。
【讨论】:
我的想法很长。我用一些示例代码更新了我的问题【参考方案2】:F# 反射 (Microsoft.FSharp.Reflection
) 是对普通 System.Reflection
API 的 F# 友好包装器,因此我认为它不会在此处添加任何内容。
引用不能定义新类型:(您需要定义一个新类型来进行基于接口的鸭子类型)
> <@ new IInterface with member x.SayHello = "hello" @>;;
<@ new IInterface with member x.SayHello = "hello" @>;;
---^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stdin(7,4): error FS0449: Quotations cannot contain object expressions
> <@ type Test() = class end @>;;
<@ type Test() = class end @>;;
---^^^^
stdin(8,4): error FS0010: Unexpected keyword 'type' in quotation literal
Reflection.Emit 仍然是解决这个问题的方法。
编辑:
我希望 F# 反射可能有一些方便的函数,以便我可以基于类型对象和一些函数值构造类型
恐怕没有。这是有关 F# 反射的文档:http://msdn.microsoft.com/en-gb/library/ee353491.aspx
【讨论】:
以上是关于在 F# 中输入通用鸭子?的主要内容,如果未能解决你的问题,请参考以下文章