受到FSharpPlus中plus
函数(相当于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
为什么在约束中添加!
会消除这种歧义?它是否告诉编译器除了方法的可能位置之外的其他东西?
答案 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编译器代码本身中允许。