2012-10-22 52 views
7

我想实现在F#鸭打字,我发现,你可以有一个member constraint in F# generics如下:F#泛型类型约束和鸭打字

type ListEntryViewModel<'T when 'T : (member Name : string)>(model:'T) = 
    inherit ViewModelBase() 

    member this.Name with get() = model.Name 

然而,上面的代码不会当我编译尝试引用该属性。我得到一个编译器错误:

This code is not sufficiently generic. The type variable ^T when ^T : (member get_Name : ^T -> string) could not be generalized because it would escape its scope.

是否可以通过通用约束实现鸭子打字?

+0

请注意,这不是“鸭子打字”,而是结构性(子)打字。 – Eyvind

回答

19

最近有一个类似的问题,其中member constraints were used in the type declaration

我不确定如何更正您的示例以使其可以编译,但如果这不可能,我不会感到惊讶。成员约束被设计为与静态解析的类型参数一起使用,特别是与函数或成员一起使用,我不认为它是惯用的F#代码,可以将它们用于类的类型参数。

我认为一个更地道的解决您的例子是定义一个接口:

type INamed = 
    abstract Name : string 

type ListEntryViewModel<'T when 'T :> INamed>(model:'T) = 
    member this.Name = model.Name 

(事实上,ListEntryViewModel可能并不需要一个类型参数,可以只取INamed作为构造参数,但也有可能是这样写它一些好处。)

现在,你仍然可以使用鸭打字和使用上的东西,有Name财产ListEntryViewModel,但没有实现INamed界面!这可以通过编写一个inline函数返回INamed和使用静态成员的约束来捕获现有Name属性来完成:

let inline namedModel< ^T when ^T : (member Name : string)> (model:^T)= 
    { new INamed with 
     member x.Name = 
     (^T : (member Name : string) model) } 

然后,您可以创建你的看法写ListEntryViewModel(namedModel someObj)模型,其中someObj没有实现接口,但只需要Name属性。

我更喜欢这种风格,因为通过接口,你可以更好地记录你需要的模型。如果你有其他对象不适合这个方案,你可以调整它们,但是如果你正在编写一个模型,那么实现一个接口是确保它暴露所有必需功能的好方法。

5

为了让您的原始代码工作:

type ListEntryViewModel< ^T when ^T : (member Name : string)>(model:^T) = 
    inherit ViewModelBase() 

    member inline this.Name with get() = (^T : (member Name : string) model) 

所以你必须标记的成员作为“内联”,并重复约束的成员函数。

我同意托马斯基于接口的方法在F#中通常是首选。

6

Is it possible to implement duck typing via a generic constraint?

否。除少数特殊情况外,F#仅实现名义打字,而鸭子打字是不可能的。正如其他答案所解释的那样,惯用的“解决方案”是将界面改造为您希望遵守该界面的所有类,但当然,在大多数情况下,如果您想要打字,这是不切实际的。

请注意,F#中的此限制是从.NET继承的。如果你想看到类似鸭子打字的更实用的解决方案,请查看OCaml的结构类型多态变体和对象。

+0

对于实际的建议+1。 – missingfaktor