ndmitchell / hlint

Haskell source code suggestions
Other
1.48k stars 195 forks source link

Suggest explicit brackets to mitigate counter-intuitive prefix negation precedence #1484

Closed kostmo closed 1 year ago

kostmo commented 1 year ago

Motivation

The operator precedence of "prefix minus" can be surprising to unsuspecting Haskell programmers.

For example, in the REPL:

ghci> -1 ^ 2
-1

and

ghci> -5 + 3
-2
ghci> plus = (+)
ghci> -5 `plus` 3
-8

The case that prompted the filing of this issue was:

ghci> -5 `mod` 3
-2

which led to inferring incorrect semantics of Haskell's mod function, illustrated in this thread. As it happens, unlike some other languages, mod a b in Haskell is non-negative for all positive b.

There is an enlightening thread on the Haskell mailing list from July 2010 that discusses fixity of the prefix minus and references (defunct?) proposals to make it bind more tightly. This corroborates the notion that the current precedence of prefix negation can be counter-intuitive.

Proposal

In contrast with existing hlint rules that suggest to remove redundant brackets, I propose that hlint suggest to add redundant brackets to disambiguate (for human readers) negation precedence.

Such a rule could catch bugs; when an author writes:

 -5 `mod` 3

hlint would suggest instead to write:

 -(5 `mod` 3)

which would cause the author to realize that their expression is being parsed differently than intended and update their code to:

(-5) `mod` 3

Implementation

The rule should fire whenever the operand of "unary minus" is an infix operation of higher precedence.

For reference, unary negation is fixity 6, the same as binary subtraction. Multiplication, division, and modulus have fixity 7, and exponentiation has fixity 8.

ndmitchell commented 1 year ago

Sounds like a good idea, given this is quite a foot gun. Patch welcome!

kostmo commented 12 months ago

I just came across this reddit post as more empirical evidence that the negation precedence can be "baffling" and a good demonstration of the value that this lint rule will provide.