我正在尝试在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;字符串)无法推广,因为 它会逃避它的范围。
是否可以通过通用约束实现鸭子类型?
答案 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#中通常首选基于接口的方法。