Note in the second case, that's not "Go" sorting NaNs to the front, that's the requested result from the passed-in function. You could create any result you wanted with a suitable choice of such function.
This isn't particularly special to Go. Most languages have "quirks", to put it charitably, around floats. Even Haskell would have similar behavior. It's really the IEEE standard that is broken, arguably; defining an entity that is not equal to itself proceeds to effectively break a huge swathe of things that you may not have even realized was based on the presumption that the identity property holds, because... why would you think about what is based on that? It's so foundational. As anyone who has dealt with NaNs for any period of time can attest, it's really difficult to work with such entities.
(Similarly, while I understand what they were trying to get at, I think SQL NULL should be equal to itself. I could be down with NULL < 1 being false and NULL > 1 being false but I'm not a huge fan of how it's NULL either. We've had many languages written since then with more conventional ideas about "invalid" values and how they participate in operators and I think the modern way almost everything else works is better, even if it arguably less "correct".)
Well, the simple answer is that constraints.Ordered is a type set which lists them: https://pkg.go.dev/golang.org/x/exp/constraints#Ordered
The other answer is of course, that float32 and float64 have a < operator, so every type listed in constraints.Ordered does, so you can use the < operator on a type parameter constrained by constraints.Orderded.
The real question you seem to be asking though, is "how does Go support < on float32 and float64 if the IEEE-754 standard says that NaN are not comparable". I can't comprehensively answer that, because I don't know that standard well enough. But for == the answer is that == is supported on floats and x == y is always false if either is NaN. Empirically, the same seems to be true for NaN <= NaN: https://go.dev/play/p/hB9CnrzpAVq
In any case, Wikipedia claims that IEEE-754 defines in fact an ordering, which is the one Go is going to use use: https://en.wikipedia.org/wiki/IEEE_754#Total-ordering_predic...
Writing "yes" or "no" (or using checkmarks and crosses) means I either have to put it on the reader to do that extra step of inference. Or that I have to re-phrase every item to be aligned in this way, while staying snappy. Or I could write "good" and "bad", of course, which seemed… overly simplistic.
Anyways, I did think about all of this and it was a conscious decision. For better or for worse. And I'm okay with it, criticism notwithstanding :)
func SortOrdered[T constraints.Ordered](s []T) {
// …
}
func Sort[T any](s []T, less func(T, T) bool) {
// …
}
For a data structure, I recommend using type SearchTree[T any] struct {
Less func(T, T) bool
// …
}
and then writing two constructors func NewSearchTree(less func(T, T) bool) *SearchTree[T] {
// …
return &SearchTree[T]{
Less: less,
// …
}
}
func NewOrderedSearchTree[T constraints.Ordered]() *SearchTree[T] {
// …
return &SearchTree[T]{
Less: func(x, y T) bool {return x < y},
// …
}
}I genuinely think I might personally converge on the `Comparator[T]` approach only (at least for types), for that reason.
As for that use-case: Go generics currently don't have a way to constrain on struct fields. So that will always have to be done with some sort of custom comparison function written by the user of your code, which returns the appropriate field.