我正在尝试在 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
但是,当我尝试引用该属性时,上面的代码将无法编译。我得到一个编译器错误:
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.
是否可以通过通用约束来实现鸭子类型?
最佳答案
最近有一个类似的问题,member constraints were used in the type declaration .
我不确定如何更正您的示例以使其编译,但如果这是不可能的,我不会感到惊讶。成员约束旨在与静态解析的类型参数一起使用,尤其是与 inline
一起使用。函数或成员,我不认为将它们与类的类型参数一起使用是惯用的 F# 代码。
我认为对您的示例更惯用的解决方案是定义一个接口(interface):
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
属性(property):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)
创建您的 View 模型。在哪里 someObj
不必实现接口(interface),只需要 Name
属性(property)。我更喜欢这种风格,因为通过采用接口(interface),您可以更好地记录您对模型的要求。如果您有其他不符合该方案的对象,您可以调整它们,但如果您正在编写模型,那么实现接口(interface)是确保它公开所有必需功能的好方法。
关于generics - F# 泛型类型约束和鸭子类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13011811/