F#泛型类型约束和duck typing

时间:2012-10-22 12:48:34

标签: generics f# inline

我正在尝试在F#中实现鸭子输入,我发现你可以拥有member constraint in F# generics,如下所示:

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

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

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

  

此代码不够通用。 ^ T时的类型变量^ T.   :(成员get_Name:^ T - &gt;字符串)无法推广,因为   它会逃避它的范围。

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

3 个答案:

答案 0 :(得分:19)

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

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

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

type INamed = 
  abstract Name : string

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

(实际上,ListEntryViewModel可能不需要类型参数,只能将INamed作为构造函数参数,但以这种方式编写它可能会有一些好处。)

现在,你仍然可以使用duck typing并在ListEntryViewModel属性的东西上使用Name,但不要实现INamed接口!这可以通过编写返回inline的{​​{1}}函数并使用静态成员约束来捕获现有的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属性。

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

答案 1 :(得分:6)

  

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

没有。除了一些特殊情况,F#只实现了无法进行鸭子打字的标称打字。正如其他答案所解释的那样,惯用的“解决方案”是将接口改装到您希望已粘贴到该接口的所有类上,但当然,在您希望进行鸭子输入的大多数情况下,这是不切实际的。

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

答案 2 :(得分: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#中通常首选基于接口的方法。