“或”在显式类型成员约束中

时间:2017-06-12 23:30:21

标签: .net generics f# polymorphism constraints

受到FSharpPlusplus函数(相当于mappend)的实现的启发,我决定复制模式并实现递增数字的泛型函数(它可能更容易使用{{ 1}},但这只是一个练习。)

LanguagePrimitives

按预期工作:

type NumberExtensions =
    static member Increment (x:float) = x + 1.0    
    static member Increment (x:int) = x + 1
    //etc

let inline increment number =
    let inline call (_:^E) (number:^N) = ((^E or ^N) : (static member Increment: ^N -> ^N) number)
    call (Unchecked.defaultof<NumberExtensions>) number //!

let incrementedFloat = increment 100.0
let incrementedInt = increment 200

不幸的是,我不明白为什么我们在约束中需要val incrementedFloat : float = 101.0 val incrementedInt : int = 201 。据我所知,约束说:

  

有一个名为or ^N的静态方法,它接受某种类型的参数(与Increment的类型相同)并返回相同类型的另一个值。必须在number^E中定义此方法。

我想:我们知道^N方法将在Increment中定义,因此我们可以安全地将^E更改为(^E or ^N)。很遗憾,此更改导致标有^E的行中出现以下错误:

  

无法根据此程序点之前的类型信息确定方法“增量”的唯一重载。可能需要类型注释。候选者:静态成员NumberExtensions.Increment:x:float-&gt; float,静态成员NumberExtensions.Increment:x:int-&gt; int

为什么在约束中添加!会消除这种歧义?它是否告诉编译器除了方法的可能位置之外的其他东西?

2 个答案:

答案 0 :(得分:2)

here对此进行了非常好的讨论。

一般来说,我们讨论的是如何访问与一系列类型相关的运算符字典,以及使用&#39; a(即素数a)定义的类型以更严格的方式处理

与往常一样,FSharp source code是一个值得关注的好地方。参见:

let inline GenericZero< ^T when ^T : (static member Zero : ^T) > : ^T =

在上面的讨论中,请参阅Jon Harrop关于类系统对Haskell性能影响的评论。在这方面,我无法评论F#的未来,但是,明确的风格默认具有一定的优势(和权衡)。

在这些时候,有必要耐心等待,看看默认值是否真的对你有帮助。然后找出你可以从这次经历得出的结论。但是,此过程可能需要6个月以上。当人们得到自己的观点时,也许有充分的理由。如果你提前采用一种做法,有时很难意识到这套好处会是什么。最后,你仍然不必同意。您的情况可能会另有要求。

答案 1 :(得分:1)

or右侧的主要预期效果是在参数本身的类型中寻找实现。

这将允许库的用户创建实现泛型方法的类型。这是一个例子:

type MyNumber = MyNumber of int with
    static member Increment (MyNumber x) = MyNumber (x + 1)

let incrementedMyNumber = increment (MyNumber 300)

因此,左侧是现有类型的实现(通常是示例中的原始类型),右侧是后面定义的类型(通常是自定义类型)。

话虽如此,你可以直接使用这种机制,只有右侧,泛型函数仍然会编译,我刚刚给你的例子可以工作(但不是你的),实际上这是典型的使用成员约束,但反之则不然,如果删除右侧编译器具有解析定义类型的所有信息,我的意思是它知道^E将永远是NumberExtensions所以它替换了类型并尝试进行重载解析,并且它会在错误消息告诉你时失败。

与其他答案相反,我不认为查看FSharp源代码对此有任何帮助,相反,它可能会令人困惑,因为它使用不同的技术来实现相同的目标:'模拟成员'假装现有类型具有该方法的实现,但这仅在FSharp编译器代码本身中允许。