12

我正在尝试在 F# 中实现鸭子类型,我发现您可以在 F# 泛型中有一个成员约束,如下所示:

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

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

但是,当我尝试引用该属性时,上面的代码将无法编译。我得到一个编译器错误:

此代码不够通用。^T : (member get_Name : ^T -> string) 时的类型变量 ^T 无法泛化,因为它会超出其范围。

是否可以通过通用约束来实现鸭子类型?

4

3 回答 3

25

最近有一个类似的问题,在类型声明中使用了成员约束

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

我认为您的示例更惯用的解决方案是定义一个接口:

type INamed = 
  abstract Name : string

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

(实际上,ListEntryViewModel可能不需要类型参数,可以直接INamed作为构造函数参数,但这样写可能会有一些好处。)

现在,您仍然可以使用鸭子类型并ListEntryViewModel在具有属性的事物上使用Name,但不要实现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)位置来创建视图模型。someObjName

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

于 2012-10-22T13:02:27.593 回答
6

是否可以通过通用约束来实现鸭子类型?

不可以。除了一些特殊情况外,F# 只实现了无法进行鸭式打字的名义打字。正如其他答案所解释的那样,惯用的“解决方案”是将接口改装到您希望遵守该接口的所有类上,但当然,在大多数情况下您想要鸭子打字是不切实际的。

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

于 2012-10-23T08:54:23.210 回答
5

要使您的原始代码工作:

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

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

所以你必须将成员标记为“内联”并在成员函数中重复约束。

我同意 Tomas 的观点,即在 F# 中通常首选基于接口的方法。

于 2012-10-22T17:04:07.363 回答