F# 通用编程 - 使用成员

4
假设我有一组支持给定成员函数的类型,例如下面的Property成员:
type FooA = {...} with

    member this.Property = ...

type FooB = {...} with

    member this.Property = ...

假设成员属性为每种类型返回一个整数。现在,我想编写一个通用函数,可以执行以下操作:
let sum (a: 'T) (b: 'U) = a.Property + b.Property

我习惯用C++编写以下内容:

template<typename T, typename U>
int sum(T a, U b)
{
    return a.Property + b.Property;
}

在F#中,等效的实现方式是什么?

3个回答

7

在F#中可以做到这一点,但这并不是惯用的做法:

let inline sum a b = (^T : (member Property : int) a) + (^U : (member Property : int) b)

一般来说,.NET泛型与C++模板非常不同,因此在尝试在F#中模拟C++之前,最好先了解它们的区别。一般的.NET哲学是通过名义类型而不是结构类型定义操作。在您的示例中,您可以定义一个公开Property属性的接口,您的两个类实现该接口,然后您的sum函数将采用该接口的实例。
有关F#泛型的更多信息,请参见http://msdn.microsoft.com/en-us/library/dd233215.aspx;有关.NET泛型与C++模板的简要比较,请参见http://blogs.msdn.com/b/branbray/archive/2003/11/19/51023.aspx(或者在Google中搜索“.NET generics C++ templates”时出现的任何内容)。

它可能不是惯用语,但它确实非常方便。例如,我使用它来轻松访问在XElement和XAttribute上定义的所有方便的显式转换运算符:let inline xval (x : ^a when ^a :> XObject) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) 然后我可以像这样做:let foo:int = xval myElement - Joel Mueller
谢谢。虽然比C++版本更冗长,但它可以工作。我在哪里可以学习有关F#中的泛型的更多知识? - Allan
@Allan - 我已经编辑了我的答案,添加了一些进一步阅读的链接。 - kvb
令人爆炸的语法。我喜欢 f#! - Rustam

2

我认为在这种情况下(即使是在F#中),清晰的方法是使用基本的面向对象编程,并定义一个接口,其中包含您需要的成员(例如Property):

type IHasProperty =
  abstract Property : int

请注意,F#推断我们正在声明一个接口,因为它只有抽象成员而没有构造函数。然后您可以在自己的类型中实现该接口:
type FooA = {...} 
  interface IHasProperty with
    member this.Property = ... 

type FooB = {...} 
  interface IHasProperty with
    member this.Property = ... 

值得一提的是,任何F#类型(包括记录、辨别联合和当然类)都可以实现接口。现在,通用函数只需要接受两个IHasProperty类型的参数即可:
let sum (a: IHasProperty) (b: IHasProperty) = a.Property + b.Property 

在这种情况下,您甚至不需要泛型,但是泛型也可以通过接口执行几个技巧 - 您可以要求类型参数T实现某些指定的接口,但是在此情况下不需要,因为当您编写sum f1 f2时,F#将自动将类似FooA的类型参数转换为接口。
在F#中使用不可变的接口和类型通常是非常明智的选择。可能有一个“更加函数式”的解决方案来解决您的问题,但这需要更多的上下文信息。

如果我不能改变代码怎么办?也就是说,类型已经实现了成员属性而不使用接口。这不是我的情况,只是好奇。 - Allan
@Allan:在这种情况下,唯一的选择可能是使用kvb建议的静态成员约束。 - Tomas Petricek
例如,在一个线性代数库中,我可以有许多种矩阵(如密集矩阵、稀疏矩阵、带状矩阵等)。在C++中,我会使用模板编写算术运算,以便适用于这些矩阵中的任何一种。例如,对于加法函数,我需要该类型提供成员运算符(i,j)来访问矩阵条目i,j。 - Allan

2

C++模板大致使用一种“结构静态子类型”关系(有些类似于“鸭子类型”),而.NET泛型使用名义子类型。正如@kvb所说,您可以通过在F#中使用inline和静态成员约束来模拟C++的东西,但要非常小心。还有其他几种表达类似关系的方式; @Tomas展示了其中一种(面向对象子类型化),另一种是传递一个方法字典(该字典知道如何从一组固定类型中投影出.Property)。 (如果这是Haskell,则可以使用类型类。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接