[swift-evolution] [Last second] Precedence of nil-coalescing operator seems too low

Xiaodi Wu xiaodi.wu at gmail.com
Thu Sep 8 01:58:32 CDT 2016


On Thu, Sep 8, 2016 at 12:17 AM, Jacob Bandes-Storch <jtbandes at gmail.com>
wrote:

> On Wed, Sep 7, 2016 at 10:02 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
>> On Wed, Sep 7, 2016 at 11:48 PM, Jacob Bandes-Storch <jtbandes at gmail.com>
>> wrote:
>>
>>> On Mon, Sep 5, 2016 at 1:19 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>>> This suggestion has been pitched earlier and I've expressed my opinion
>>>> in those earlier threads, but I'll repeat myself here:
>>>>
>>>> I'm hugely opposed to such changes to the precedence table. Those of
>>>> us who work with bitwise operators on a regular basis have memorized their
>>>> precedence in Swift (and other languages) and rely on such precedence to
>>>> write readable, correct code without excessively nested parentheses.
>>>>
>>>
>>> Could you point me towards some examples of such code? I don't write it
>>> very often, so I don't feel I can really evaluate this. (This seems
>>> analogous to the "terms of art" categorization from the API Design
>>> Guidelines threads.) Much of the code I would normally write using bitwise
>>> operators has been replaced with the SetAlgebra protocol methods provided
>>> on OptionSet types.
>>>
>>
>> Gladly. These (which cannot be copied verbatim into Swift, as C operator
>> precedences are different):
>> https://graphics.stanford.edu/~seander/bithacks.html
>>
>> Lest you think I'm giving you a C example because I don't actually use
>> such things in Swift, here's me using some of these:
>> https://github.com/xwu/FlowKit/blob/master/Source/BitVector.swift
>>
>> (Note that this specific example will soon be obsolete with new integer
>> protocols.)
>>
>
> Both of these actually seem to make pretty careful use of parentheses in
> expressions with mixed-precedence infix operators.
>

I do try to be very careful, yes :) If I recall correctly, I have actually
hardcoded the order of operations entirely with parentheses, disregarding
Swift's precedence table entirely. The reason for this was twofold: (1) at
the time I first wrote the code, it was the very first time I'd used
Swift's bitwise operators, which have different precedences from C; (2) I
was porting over a numerical recipe from C, and I preserved the parentheses
that were required in that language for my own sanity.

I shared that example in part because I think it illustrates a salient
point here. Chris Lattner--in his comment in the previous thread on the
topic--challenged those proposing this change to show "evidence of common
bugs" or "evidence of actual user confusion." By contrast, my experience
has been (both from reading and writing code) that there's typically very
good code hygiene in instances where mixed precedence operators are used in
practice, and (with respect to writing code) I certainly don't consider
myself an unusually careful programmer.

Clearly, no one disputes that parentheses can be wise in some instances
even when they're not required. Essentially, by proposing removal of
precedence relations, the question you're raising is: should parentheses
always be required [in some subset of circumstances]?

My take on this issue, if I may rephrase, is simply this:

1. When I've been confused about the order of operations as I'm *writing*
code, I've put in parentheses without hesitation. From what I've gathered
from this list, essentially everyone else does this as well. Would removing
precedence relations improve this aspect of using operators (i.e. writing
code)? It does not seem so, since it would make required what people
already use according to their best judgment.

2. When I've been confused about the order of operations as I'm *reading*
code, I simply turn to the precedence table. Three points on the experience
of reading code using operators:
(a) In practice, I've rarely been confused about the order of operations,
since (as mentioned above) I find that those who write the code tend to
clarify for the reader anyway, if only for their own sanity while writing
the code.
(b) When I've found myself confused, as in the case with some of the "Bit
Hacks" linked above, this issue has been the least of my problems, as
formulas that mix these operators have proved difficult to digest even with
all the parentheses put in.
(c) It's well nigh impossible to read the precedence table incorrectly: the
_concept_ of operator precedence must be second-nature to any person who
knows what multiplication is, so it's simply a matter of finding out what
the actual precedence _is_; no ambiguity persists after that information is
supplied.

In the former, I find only a small handful of cases where & is mixed with
> +, *, or / — seemingly the minority.
>

I haven't gone through to tabulate what proportion of those formulas mix
certain categories of operators with others. It's not a particularly useful
piece of information, I'd argue, since no one is implementing this entire
book of recipes and calling each function a roughly equal number of times
in their code. The subset of these recipes that I've studied (or
equivalently, have been interested in using or actually have used) mix
operators pretty frequently. It all comes down to what code you're writing.
In some ways, this gets to another tentpole of my argument to preserve
precedences. In some ways, all arguments about their addition, removal, or
modification, seem to have boiled down to a subjective judgment as to their
current appropriateness or how common their use or misuse might be, but
with strikingly little data for evidence.

Case in point (without meaning to calling you out specifically; just that
our conversation is the freshest in my mind, and it's late and my mind is
foggy): you mentioned that you were content with the current precedence of
bitwise <<, because you could see how it was related to exponentiation; but
you were not happy with the precedence of &, because you could not see how
it was related to multiplication. However, by the same token, I can see
both how << is related to exponentiation and how & is related to
multiplication, and thus I am content with their precedence. The
counter-counterargument in the previous thread was that the relationship
between & and multiplication was not sufficiently obvious or relevant, but
of course that's in turn also a subjective judgment. I can equally argue
that the relationship between << and exponentiation is not sufficiently
relevant either, or that the relationship between & and multiplication is
crucially relevant...

Given that Swift has already revised operator precedence (as compared to
the C family of languages), and given that Swift 4 makes some promise of
avoiding source-breaking changes where possible, the above style of
argumentation seems woefully insufficient to justify another change.
Rather, I could be swayed by objective evidence that users *commonly*
expect some operator to work a certain way, leading to actual erroneous
real-world code. However, it does not seem that such evidence is
forthcoming. By contrast, let's visit some of the not-so-real-world
examples we've seen so far:

Erica thought that `let y = 5 + (x ?? 2)` required one too many sets of
parentheses. Removal of the precedence relation between ?? and arithmetic
operators does nothing to address this gap between Swift's syntax and
Erica's expectations. In fact, we would be making the parentheses mandatory
in more scenarios than are required currently.

Let's revisit some of Vladimir's examples:

I believe that `let result = a || (b) ? isOne() : isTwo()` is implausible
for several reasons. I will give only two. First, I have never encountered
any actual attempt to evaluate four Boolean values in some combination of a
binary operator and the ternary operator. Second, it is unlikely that a
writer of code would try _but fail_ at correctly surrounding the intended
portions of that statement with parentheses (i.e. either `(a || b)` or `(b
? isOne() : isTwo())`), instead ineffectually surrounding `b` with
parentheses.

`s << (x == 10) ? "10" : "not 10"` is impossible. Firstly because no such
`<<` operator is defined in the standard library or core libraries, and I
do not know of any Swift library that does define such an operator.
Secondly because Swift greatly discourages (to my knowledge) the
overloading of operators with entirely different semantics. Thirdly because
even if one such operator is defined but the order of operations is not as
the writer intended, the expression would not compile because of a type
mismatch. Fourthly, because even if two such operators are defined so that
the expression would compile, the result would differ in type (from the
expected type) if the order of operations is not as the writer intended,
and any further non-trivial use of the result would lead to an error at
compile time.

You and I seem to agree that `let i = 4 << 1 + 3` is rather reasonable. But
we needn't use such a subjective judgment. Quite simply, you would not
write such a line of code if you weren't sure of the precedence of <<
relative to +. You'd use parentheses. You wouldn't read such a line of code
just assuming a certain precedence relation if you were foggy on
bitshifting operators; you would look it up. I am not sure how one can make
an unintended error either reading or writing this line of code[*].

[*] Actually, I can see precisely one way to make such an unintended error,
since I have actually done so. It has to do with Swift having moved away
from the classical C-family operator precedence table. I would actually not
be opposed to making changes that guard against such an error.


>> The proposals include both breaking precedence relations and changing
>> them; changing them will essentially always cause silent changes in
>> existing code. Removing relations won't; however, it will necessitate
>> sometimes difficult migrations, as formulas are complicated.
>>
>
> I would think (hope) that any removals could easily yield automatic
> migrations by adding parens where they were previously implied. I would
> only be concerned with the possibility that such a migration would affect
> type-checking speed (an area that already needs improvement, and can be
> exacerbated by large expressions).
>

Certainly, automatic migrations will help avoid certain errors. Our frail
human brains make certain mistakes possible when adding or removing
parentheses by hand. Things like distributing the minus sign, etc...
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160908/a0417f73/attachment.html>


More information about the swift-evolution mailing list