简体   繁体   English

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

[英]“or” in explicit type member constraints

Inspired by the implementation of the plus function (equivalent to mappend) in FSharpPlus , I decided to copy the pattern and implement a generic function incrementing numbers (it would probably be easier to use LanguagePrimitives , but it's just an exercise). 受FSharpPlus中plus函数(相当于mappend)的实现启发 ,我决定复制模式并实现递增数字的通用函数(使用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

It works as expected: 它按预期工作:

val incrementedFloat : float = 101.0
val incrementedInt : int = 201

Unfortunately, I don't quite get why do we need or ^N in the constraint. 不幸的是,我不太明白为什么我们需要or ^N的约束。 As far as I understand, the constraint says: 据我了解,约束条件如下:

There's a static method named Increment , which takes an argument of some type (the same type as the number 's) and returns another value of the same type. 有一个名为Increment的静态方法,该方法接受某种类型的参数(与number相同的类型),并返回另一个相同类型的值。 This method must be defined either in ^E or in ^N . 此方法必须在^E^N

Here I thought: We know that the Increment method will be defined in ^E , so we can safely change (^E or ^N) to just ^E . 在这里我想:我们知道Increment方法将在^E定义,因此我们可以安全地将(^E or ^N)更改为^E Unfortunately, the change resulted in the following error in the line marked with ! 不幸的是,更改导致在标有!的行中出现以下错误! :

A unique overload for method 'Increment' could not be determined based on type information prior to this program point. 无法基于此程序点之前的类型信息来确定方法“增量”的唯一重载。 A type annotation may be needed. 可能需要类型注释。 Candidates: static member NumberExtensions.Increment: x:float->float, static member NumberExtensions.Increment: x:int->int 候选人:静态成员NumberExtensions.Increment:x:float-> float,静态成员NumberExtensions.Increment:x:int-> int

Why does adding or ^N to the constraint removes this ambiguity? 为什么在约束中添加or ^N消除了这种歧义? Does it tell the compiler something else than the possible location of the method? 它是否告诉编译器该方法可能的位置以外的其他信息?

There's a really good discussion about this over here . 有一个关于这个在一个很好的讨论在这里

In general, we're talking about how to access a dictionary of operators that relate to a family of types, and that types defined with 'a (ie. prime a) are handled in a stricter way. 通常,我们正在谈论如何访问与一族类型有关的运算符字典,并且用'a(即素数a)定义的类型将以更严格的方式处理。

As always, the FSharp source code is a good place to look. 与往常一样, FSharp源代码是一个不错的地方。 See: 看到:

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

In the discussion above, see Jon Harrop's comment about the impact of the class system on Haskell performance. 在上面的讨论中,请参阅Jon Harrop关于类系统对Haskell性能的影响的评论。 I can't comment about F#'s future in this regard, but, having an explicit style default comes with certain advantages (and trade-offs). 在这方面,我无法评论F#的未来,但是拥有明确的样式默认值会带来某些优势(以及权衡取舍)。

It can be useful at times like these to be patient with the quirks to see if the defaults actually help you. 在这种情况下,耐心等待一些怪癖,看看默认值是否对您有帮助,这可能会很有用。 Then work out what conclusions you can draw from this experience. 然后找出可以从该经验中得出什么结论。 This process might take 6+ months, though. 不过,此过程可能需要6个月以上的时间。 When people get opinionated, perhaps there's a good reason for it. 当人们发表意见时,也许有充分的理由。 Sometimes it's hard to realise what the set of benefits will be, should you adopt a practice, ahead of time. 有时候,如果您提前采取一种做法,那么很难意识到这套好处是什么。 At the end of all that, you still don't have to agree. 最后,您仍然不必同意。 Your case could demand otherwise. 您的情况可能会要求其他。

The main expected effect of the right side of the or is to look for an implementation in the type of the argument itself. or右侧的主要预期效果是寻找参数本身类型的实现。

This will allow for instances the users of your library to create types that implement your generic method. 这样,实例库的用户就可以创建实现您的通用方法的类型。 Here's an example: 这是一个例子:

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

let incrementedMyNumber = increment (MyNumber 300)

So, the left side is needed for implementations of existing types (typically primitive types as in your example) and the right side for types later defined (typically custom types). 因此,左侧是实现现有类型(通常是示例中的原始类型)的实现,而右侧是稍后定义的类型(通常是自定义类型)的实现。

Having said that, you can use this mechanism the straight forward way, which is only with the right side and the generic function will still compile and the example I just gave you will work (but not yours), actually that's the typical use of member constraint, but the opposite is not true, if you remove the right side the compiler has all the information to resolve the type at the definition, I mean it knows that ^E will always be NumberExtensions so it substitutes the type and tries to do overload resolution, and it fails as the error message tells you. 话虽如此,您可以直接使用此机制,仅在右侧使用,并且泛型函数仍将编译,而我刚刚给您的示例可以使用(但不能使用您的示例),实际上这是成员的典型用法约束,但事实并非如此,如果您删除右侧,则编译器将具有所有信息以在定义时解析类型,我的意思是它知道^E始终为NumberExtensions因此它将替换类型并尝试执行重载解决方案,并且由于错误消息提示您而失败。

Contrary to the other answer, I don't think that looking at the FSharp source code is of any help here, on the contrary, it might be confusing since it uses a different thechnique to achieve the same: 'simulated members' which pretends that existing types have an implementation of the method but this is only allowed in the FSharp compiler code itself. 与其他答案相反,我认为在这里查看FSharp源代码没有任何帮助,相反,这可能会造成混淆,因为它使用不同的技术来实现相同的目的:“模拟成员”假装现有类型具有该方法的实现,但这仅在FSharp编译器代码本身中允许。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM