F#泛型类型约束和duck typing

Noe*_*edy 9 generics f# inline

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

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

  member this.Name with get() = model.Name
Run Code Online (Sandbox Code Playgroud)

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

此代码不够通用.^ T :(成员get_Name:^ T - >字符串)时的类型变量^ T无法一般化,因为它会逃避其范围.

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

Tom*_*cek 23

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

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

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

type INamed = 
  abstract Name : string

type ListEntryViewModel<'T when 'T :> INamed>(model:'T) =  
  member this.Name = model.Name
Run Code Online (Sandbox Code Playgroud)

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

现在,你仍然可以使用duck typing并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) }
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过书面形式创建视图模型ListEntryViewModel(namedModel someObj),其中someObj没有实现接口,但需要只是Name财产.

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


Jon*_*rop 6

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

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

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


wme*_*yer 5

要使您的原始代码有效:

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

    member inline this.Name with get() = (^T : (member Name : string) model)
Run Code Online (Sandbox Code Playgroud)

因此,您必须将成员标记为"内联"并在成员函数中重复约束.

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