Why Does Ruby '**' Operator Have Higher Precedence Than Unary '-'

Why does Ruby `**` operator have higher precedence than unary `-`?

Many languages define their operator precedence tables by modeling after mathematics' order of operations. In math, exponentiation does have higher precedence than multiplication, and unary negation is a multiplication, after all.

From matz in a reply to "the sign of a number is omitted when squaring it":

People with mathematical background demands precedence for ** being
higher than that of unary minus. That's the reason.

Operator precedence of unary operators

My programming ruby book (2nd edition) also lists unary operators as having higher precedence than assignment.

The unary operator IS being given highest precedence. The reason the line is parsed as ~ (a = 1) is because decomposing the line into valid syntax is of higher precedence than anything else, including using the simple variable 'a' as the expression the unary operator operates on.

If the ruby parser could have made something valid of the rest of the line, it would have used (~ a), but there is no valid rule than matches = something, only lvalue '=' rvalue.

You can regard "valid syntax" as the top priority, then simple values, constant and variable names and then the standard operators under that.

Is unary minus supposed to have higher precedence for numeric literals?

The reason is that in the literal number case, the - in front isn't an unary operator, but part of the literal syntax.

However, the - operator itself has lower precedence than method invocation. Given that there is no -'string' literal syntax for strings, this rule always applies regardless of if the string was literal or not.

class Integer
def -@
puts 'Called'
end
end

class String
def -@
puts 'Called'
end
end

-1 # nothing, the - wasn't an unary operation, but part of the number construction
x = 1
-x # Called

-'a' # Called
a = 'a'
-a # Called

Another interesting thing is that if you put a space between the number and the -, the - is no longer part of the literal syntax.

- 1 # Called

Here is the semantic explanation:

  • There is such thing as "the number negative one". It makes sense that there should be literal syntax for it (as there is for any positive number). And the most intuitive syntax for it is -1.
  • We still want to be able to call the unary operator on literal positive numbers. The most intuitive (and easy to implement) way for that would be not to make the parser super fancy as to ignore any random amount of whitespaces in the literal syntax for negative numbers. Hence why - 1 accounts to "apply the unary minus to the number (positive) one".
  • There is no such thing as "the string negative 'a'". That is why -'a' means "apply the unary minus to the string 'a'".

Why is the precedence of exponentiation lower than that of unary plus minus?

According to Wikipedia, the mathematical convention is that unary minus has a lower precedence than exponentiation. Some programming languages follow this, others don't.

But the above article also gives examples of different conventions for mathematical notation used in scientific publications1; e.g. the precedence of different ways of writing multiplication and division.


You asked: Why?

Well, in most cases there isn't a clear rationale for why particular language designers made particular choices; see the answers to this Q&A. However, we certainly can't justify the position that any particular precedence system is "correct" from a theoretical stand-point.

In general, the guiding principles for PL precedence systems seem to be:

  • Try to be consistent with the ancestors for this language.
  • Try to be consistent with perceived mathematical convention.
  • Do what "feels right" at the time.

The results are not consistent.

Fortunately:

  1. people tend to get used to the quirks of the languages that they use most of the time, and

  2. the exponentiation operator is not used very often2, and even less often with unary minus.

So it doesn't usually matter. (Except when someone gets it wrong in a context that has huge impact / consequences. And even then, there should be processes in place to deal with human error.)


The operator precedence for expression evaluation in bash is documented as being based on C operator precedence. (See man bash.) C doesn't have an exponentiation operator, but it does make unary + and - higher precedence than multiplication and division.

So in order to be consistent with C, the bash implementors needed to put the operator precedence of ** above *, / and % and below unary -. (Putting ** above unary - goes against the clear intent of C ... which is that unary - is above all other arithmetic operators.)


If your real question is not "why did they do it" but "are the reasons documented", you will probably need to trawl through developer mailing lists, source code repositories and so on for clues. Or maybe try asking the designers ... though they may not remember the reasons accurately.


1 - If mathematicians can't be consistent about notation, why is it a big deal that programming language designers aren't either?

2 - Indeed, many programming languages don't even support an exponentiation operator.

Ruby operator precedence table

Ruby 2.1.0, 2.0, 1.9, 1.8

An operator is a token that represents an operation (such as addition or comparison) to be performed on one or more operands. The operands are expressions, and operators allow us to combine these operand expressions into larger expressions. (Ref)

N = arity = The number of operands the operator operates on. (Ref)

A = associativity = The order of evaluation when the same operator (or operators with the same precedence) appear sequentially in an expression. The value L means that expressions are evaluated from left to right. The value R means that expressions are evaluated from right to left. And the value N means that the operator is nonassociative and cannot be used multiple times in an expression without parentheses to specify the evaluation order. (Ref)

M = definability = Ruby implements a number of its operators as methods, allowing classes to define new meanings for those operators. Column M of specifies which operators are methods. Operators marked with a Y are implemented with methods and may be redefined, and operators marked with an N may not. (Ref)

The following table is ordered according to descending precedence (highest precedence at the top).

N A M  Operator(s)            Description
- - - ----------- -----------
1 R Y ! ~ + boolean NOT, bitwise complement, unary plus
(unary plus may be redefined from Ruby 1.9 with +@)

2 R Y ** exponentiation
1 R Y - unary minus (redefine with -@)

2 L Y * / % multiplication, division, modulo (remainder)
2 L Y + - addition (or concatenation), subtraction

2 L Y << >> bitwise shift-left (or append), bitwise shift-right
2 L Y & bitwise AND

2 L Y | ^ bitwise OR, bitwise XOR (exclusive OR)
2 L Y < <= >= > ordering

2 N Y == === != =~ !~ <=> equality, pattern matching, comparison
(!= and !~ may not be redefined prior to Ruby 1.9)

2 L N && boolean AND
2 L N || boolean OR

2 N N .. ... range creation (inclusive and exclusive)
and boolean flip-flops

3 R N ? : ternary if-then-else (conditional)
2 L N rescue exception-handling modifier

2 R N = assignment
2 R N **= *= /= %= += -= assignment
2 R N <<= >>= assignment
2 R N &&= &= ||= |= ^= assignment

1 N N defined? test variable definition and type
1 R N not boolean NOT (low precedence)
2 L N and or boolean AND, boolean OR (low precedence)
2 N N if unless while until conditional and loop modifiers

Operator precedence of assignment and conditional operators

Higher precedence of assignment means that your expression evaluates to (x = 5) if false, and not to x = (5 if false). Note, that later is a perfectly valid expression too.

Whether each particular clause is executed is determined by language rules. E.g., in a ternary operator a ? b : c, only b or c will be executed, but not both.

edit
About the difference.

In x = (5 if false), assignment is processed first. But to complete it, we need left part of assignment, which is nil, because 5 if false evaluates to nil. So, the expression is equivalent of x = nil.

In (x = 5) if false, conditional operator is processed first. According to its rules, we first have to evaluate condition (false). Since it's false, there's nothing more to do and result of evaluation is nil.

Hope that's clear.

What's the precedence of method calls with and without parentheses?

Prelude

This aims to test all possible scenarios.

Note that when saying "operator X has higher precedence than method invocation" what is meant is in arguments. Aka:

invocation foo X bar

as opposed to (call on object)

X invocation

As far as the second case is concerned, method calls always have higher precedence.


Short answer

It doesn't fit:

  • It causes SyntaxError in some cases
  • It has higher precedence than rescue, but lower than assignment

Summary

  • not can't be used after method invocation regardless of brackets
  • Using brackets (()) with method invocations sometimes causes a SyntaxError. These cases are: and, or, if, unless, until, while and rescue
  • In cases when brackets don't cause an error, they don't change the precedence in any way
  • All operators, except for and, or, postfix if, unless, until, while, rescue have higher precedence than method invocation

Lets try it:

class Noone < BasicObject
undef_method :!

def initialize(order)
@order = order
end

def method_missing(name, *args)
@order << name
self
end
end

First unary:

# + and - will become binary
unary_operators = %i(! ~ not defined?)

puts 'No brackets'
unary_operators.each do |operator|
puts operator

order = []
foo = Noone.new order
bar = Noone.new order
begin
eval("foo.meta #{operator} bar")
rescue SyntaxError => e
puts e
end
p order
puts '-----------'
end

puts 'Brackets'
unary_operators.each do |operator|
puts operator

order = []
foo = Noone.new order
bar = Noone.new order
begin
eval("foo.meta(#{operator} bar)")
rescue SyntaxError => e
puts e
end
p order
puts '-----------'
end

Points taken:

  • not after a method invocation is a SyntaxError
  • all unary operators have higher precedence than method invocation regardless of brackets

Now binary:

binary_operators = %i(
**
* / %
+ -
<< >>
&
| ^
> >= < <=
<=> == === =~
.. ...
or and
)

puts 'No brackets'
binary_operators.each do |operator|
order = []
foo = Noone.new order
bar = Noone.new order
baz = Noone.new order
begin
eval("foo.meta bar #{operator} baz")
rescue SyntaxError => e
puts e
end
p order
end

puts 'Brackets'
binary_operators.each do |operator|
order = []
foo = Noone.new order
bar = Noone.new order
baz = Noone.new order
begin
eval("foo.meta( bar #{operator} baz)")
rescue SyntaxError => e
puts e
end
p order
end

Points taken:

  • brackets around method invocation with and or or is a SyntaxError
  • we have to test and and or further without brackets
  • .. and ... call <=>. We have to test this further
  • we couldn't test a few other binary operators this way, namely &&, ||, ==, !=, modifier rescue, if, unless, until, while
  • other than the above mentioned, operators have higher precedence, regardless of brackets

def yes
puts 'yes'
true
end

def no
puts 'no'
false
end

def anything(arg)
puts 'Anything'
arg
end

anything yes and no
anything no or yes
anything yes && no
anything no || yes
anything(yes && no)
anything(no || yes)

anything yes == no
anything(yes == no)
anything yes != no
anything(yes != no)

Points taken:

  • and and or have lower precedence without brackets
  • &&, ||, == and != have higher precedence regardless of brackets

def five(*args)
p args
5
end

five 2..7
five(2..7)
five 2...7
five(2...7)

Points taken:

  • .. and ... have higher precedence regardless of brackets

anything yes if no
anything(yes if no)
anything no unless yes
anything(no unless yes)

anything no until yes
anything(no until yes)
anything yes while no
anything(yes while no)

Points taken:

  • brackets with if, unless, until, while cause a SyntaxError
  • all of the above have lower precedence than method invocation without brackets

def error
puts 'Error'
raise
end

anything error rescue yes
anything(error rescue yes)

Points taken:

  • brackets around rescue cause a SyntaxError
  • rescue has lower precedence if no brackets are present

Ternary:

anything yes ? no : 42
anything(yes ? no : 42)

Points taken:

  • ternary has higher precedence regardless of brackets

Assignment (left for last as it changes yes and no):

anything yes = no
anything(no = five(42))

Points taken:

  • Assignment has higher precedence than invocation

Note that += and the like are just shortcuts for + and = so they exhibit the same behaviour.

If post decrement have higher precedence than pre increment in JAVA then why does the following code output as 22.0 and not as 20.0 .?

Precedence rules tell you how expressions are parsed. Post decrement having higher precedence means that ++quantity + quantity-- must be parsed as

((++quantity) + (quantity--))

And not for example as the following which doesn't even make sense

((++quantity) + quantity)--

The evaluation order is unaffected. The expression is still evaluated left to right. That is, left branch of the + before the right branch. That means ++quantity must be evaluated before quantity--.



Related Topics



Leave a reply



Submit