-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: add spaceship operator <=> #45319
Comments
I suggest this because, while I actually find the thing where true and false aren't numeric values and can't be Alternatively:
|
Isn't this sort of thing what the years of work on parametric types is supposed to solve? :) |
Why mandate that the nonzero return values be -1 or 1, as opposed to just negative or positive respectively? With the latter approach, |
The most straightforward version without the operator is much easier to understand for me. Probably stems from the fact that it uses regular mathematical operators. It seems harder to human-parse the version with the spaceship operator. A generic comparison function if can be defined would be better. |
I actually sort of like if this didn't exist, and i showed someone a program using it, and they otherwise knew Go, i don't think they'd be confused by it. i was also tempted at one point by proposing something like a oh, and i think the reason to mandate -1/1 is so you can switch on them. (and now i want to be able to use -1 as an index into a thing, but i see problems here.) if you did it with arbitrary ranges, the switch would be:
but at that point, you might as well just write
and i just realized that's wrong and i'm leaving the error here to point out: yes, the spaceship operator DOES prevent mistakes actual programmers can make even while thinking about the problem. |
@seebs |
as syntax is always a bit contentious, I'd propose to recognize that fact as a primordial law of human nature and call it a tie. actually, make that the tie starship: (an improved version of |
I'm not sure if the example given is the most common use case, because if it is then I think for the sake of one line of code (the original was 6 loc the new operator version is 5 loc) the code clarity is reduced for not much benefit. I personally much prefer the current go implementation with explicit conditional blocks. |
One upside I can see to an explicit Line 13 in 0e85fd7
|
Is time.Time at least partially ordered if we take into account timezones? |
What advantage does the proposed solution offer with respect to: if cmp := pairs[i].length - pairs[j].length; cmp < 0 {
return true
} else if cmp > 0 {
return false
} ? |
Or we could also introduce a ternary logic predeclared type. (Also useful for optional bools.) |
That the proposed solution doesnt return false when |
Another option is to have an operator, perhaps <> or <=>, which randomly chooses whether the comparison succeeds or fails. This could be used to drive high-quality tests for fault-tolerant programming. |
@martisch Ok. That, and being able to say that Go has a spaceship operator, I think are reasons enough to support this proposal. |
Readability is not always an easy thing to reason about. And as such I currently haven't seen an improvement that switch pairs[i].length <=> pairs[j].length {
case -1:
return true
case 1:
return false
} versus: switch {
case pairs[i].length < pairs[j].length:
return true
case pairs[i].length > pairs[j].length:
return false
} |
Consider:
It could take a while to spot the bug there. That's the only real benefit of the thing, I think -- lets you do the comparison once and get both results, making it impossible to accidentally use two slightly incompatible/different comparison terms. |
I'd like to see the spaceship operator hidden behind a new option to the Go compiler. As I see it, any program that uses the operator MUST be compiled as
otherwise it is a compile-time error. |
Not to bikeshed, but should it perhaps be written |
@seebs indeed that bug is quite hard to catch.
|
Jokes aside, I think that if we do add this to Go we should spell it as |
func Min(a, b int) int {
return (a&(a<=>b)^(b<=>a)&b^1)&((a<=>b)|(b<=>a)) |^ ((a<=>b)|(b<=>a))&a
}
func Max(a, b int) int {
return (a&(b<=>a)^(a<=>b)&b^1)&((a<=>b)|(b<=>a)) |^ ((a<=>b)|(b<=>a))&a
} Or, if writing more lines of code is forgivable, an option that works for any ordered type: type T = int // or float64, string, ...
func v0(a, b T) T { return a }
func v1(a, b T) T { return b }
func Min(a, b T) T {
// Every ASCII bracket character on one line -- stunning!
return [3](func(a, b T) T){v0, v0, v1}[1+(a<=>b)](a, b)
}
func Max(a, b T) T {
return [3](func(a, b T) T){v0, v0, v1}[1-(a<=>b)](a, b)
} Notably, if A question, though: what is the result of |
i would expect |
Unicode has 🛸, which might be more appropriate given the similarity. |
So... was this a real proposal or an April fools joke? 🧐 |
Returning multiple values from the operator is a possibility, but it makes it considerably harder to use the operator in an expression. |
Much though I like it as a new thing for operators to do, for it to be usable in general you'd need a way to generally handle expressions producing multiple values in expressions... Although come to think of it, I'm not sure how you'd use it in an expression, in general. Like, in an expression, you'd presumably want one or the other of the values. This gets into the same space as |
I did indeed interpret this as an April fools' joke. But, as my previous comment demonstrates and @DmitriyMV alluded to, I think that the proposal is very abusable. If I'm not mistaken, it makes it possible to implement any combinational circuit. While this is not quite the same power as the C family's Also, my question from my previous comment still stands. What is the result of |
I'm sorry, but I can't get behind that rationale with something this trivial. It's still possible to make a mistake, even with |
If it happens frequently enough, this might be a good candidate for a vet (or staticcheck) check. |
I am absolutely a fan of replacing pairs of However, I am not a fan of the For example, with a predeclared if ord := cmp(pairs[i].length, pairs[j].length); ord < 0 {
return true
} else if ord > 0 {
return false
} or (my preferred idiom): if ord := cmp(pairs[i].length, pairs[j].length); ord != 0 {
return ord < 0
} |
It is also guaranteed not to overflow — even if the things being compared may include large negative values. It is also less prone to transposition bugs. When you want the semantics of |
The code in compress/bzip2/huffman.go is difficult to read because the repetition of
It makes clear that this is a less function, and the special case is For reference, this is the original code:
So the motivating example from the real world doesn't convince me that a spaceship is necessary for clarity. |
I actually sort of like the built-in function. Downside, the name is probably clashy with stuff, but we already have that with predeclared identifiers all the time. (It took me at least a year to stop writing |
This operation comes up most often in an ordered sequence of comparisons: if a1 != a2 return a1 < a2, else if b1 != b2 return b1 < b2, else return false. Perhaps there's a cleaner way to write that entire construction using generics, thus obviating the need for new language support. I'm imagining a callsite roughly like: I'm not entirely sure how to actually write that function signature though. Hopefully I'm missing something obvious. |
You could almost do this with a hypothetical |
|
@josharian, I don't think a generic (Short-circuiting is often very helpful for comparators, and short-circuiting in a function like that requires some form of lazy evaluation, which I don't see Go ever acquiring.) |
Instead of
So Not sure if that would cover most of the intended uses of |
@randall77 as you know, there are some subtleties around short-circuiting and interfaces. Do they panic if short-circuited? If not, there's an asymmetry vs struct equality. Not a showstopper, but unfortunate. Struct field ordering matters for type identity, reflection, etc. So I'm not sure adding a dependency on it for ordering hurts too much. There's also a proposal from @martisch to support array ordering. Can't find it at the moment. |
This is going to be a pure syntactic matching check, and a StaticCheck checker seems more appropriate. The frequency may not be high since such typos will not pass the unit tests. |
I think if we defined struct ordering with
|
Syntactic ambiguities aside, if there was a
such that the result is For instance, given two source "positions"
But @randall77 's suggestion (comparing of structs) would do the same thing. |
The advantage of |
I don't see why the original proposal can't just be something like In
And the original example with
|
Based on the discussion and the emoji voting, this is a likely decline. Leaving open for four weeks for final comments. |
No further comments. |
It's moderately common in any programming language to write code like (from compress/bzip2/huffman.go):
Perl has a special operator for this kind of code:
<=>
, known as the spaceship operator. The spaceship operator is also available in C++20, Ruby, and PHP.In Go, the new operator
<=>
would be permitted for any two valuesa
andb
. It would follow the same type rules as for any ordered comparison. The expressiona <=> b
would evaluate to an untyped integer value. The value would be-1
ifa < b
,1
ifa > b
, and0
ifa == b
. For code like the above it would be used asor as
The operator lets code capture all aspects of a comparison in a single expression. The cost is that in the typical case additional comparisons are required to use the result, but those additional comparisons are simpler, and the code is easier to read because there is no need to verify that the different comparisons are using exactly the same values.
The text was updated successfully, but these errors were encountered: