F# 泛型类型约束和鸭子类型 [英] F# generic type constraints and duck typing
问题描述
我正在尝试在 F# 中实现鸭子输入,我发现你可以有一个 F#泛型中的成员约束如下:
I'm trying to implement duck typing in F# and I spotted that you can have a member constraint in F# generics as follows:
type ListEntryViewModel<'T when 'T : (member Name : string)>(model:'T) =
inherit ViewModelBase()
member this.Name with get() = model.Name
但是,当我尝试引用该属性时,上面的代码将无法编译.我收到编译器错误:
However, the above code won't compile when I try to reference the property. I get a compiler error:
此代码不够通用.^T 时的类型变量 ^T: (member get_Name : ^T -> string) 不能泛化,因为它会超出它的范围.
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.
是否可以通过通用约束实现鸭子类型?
Is it possible to implement duck typing via a generic constraint?
推荐答案
最近有一个类似的问题 在类型声明中使用了成员约束.
There was a similar question recently where member constraints were used in the type declaration.
我不确定如何更正您的示例以使其编译,但如果这是不可能的,我不会感到惊讶.成员约束旨在与静态解析的类型参数一起使用,尤其是与 inline
函数或成员一起使用,我认为将它们与类的类型参数一起使用并不是惯用的 F# 代码.
I'm not sure how to correct your sample to make it compile, but I would not be surprised if that was not possible. Member constraints are designed to be used with statically resolved type parameters and especially with inline
functions or members and I do not think it is idiomatic F# code to use them with type parameters of a class.
我认为对您的示例更惯用的解决方案是定义一个接口:
I think that a more idiomatic solution to your example would be to define an interface:
type INamed =
abstract Name : string
type ListEntryViewModel<'T when 'T :> INamed>(model:'T) =
member this.Name = model.Name
(实际上,ListEntryViewModel
可能不需要类型参数,只需将 INamed
作为构造函数参数即可,但将其写入可能会有一些好处这样.)
(In fact, the ListEntryViewModel
probably does not need a type parameter and can just take INamed
as a constructor parameter, but there may be some benefit in writing it in this way.)
现在,您仍然可以使用duck 类型并在具有Name
属性的事物上使用ListEntryViewModel
,但不实现INamed
接口!这可以通过编写一个返回 INamed
并使用静态成员约束来捕获现有 Name
属性的 inline
函数来完成:
Now, you can still use duck typing and use ListEntryViewModel
on things that have Name
property, but do not implement the INamed
interface! This can be done by writing an inline
function that returns INamed
and uses static member constraints to capture the existing 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)
来创建您的视图模型,其中 someObj
不必实现接口,而只需要 Name
属性.
You can then create your view model by writing ListEntryViewModel(namedModel someObj)
where someObj
does not have to implement the interface, but needs just the Name
property.
我更喜欢这种风格,因为通过使用界面,您可以更好地记录您对模型的需求.如果您有其他不适合该方案的对象,您可以调整它们,但如果您正在编写模型,那么实现接口是确保它公开所有必需功能的好方法.
I would prefer this style, because by taking an interface, you can better document what you require from the model. If you have other objects that do not fit the scheme, you can adapt them, but if you're writing a model, then implementing an interface is a good way to make sure it exposes all the required functionality.
这篇关于F# 泛型类型约束和鸭子类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!