Skip to content

Operators

datanorris edited this page Jan 28, 2016 · 25 revisions

Class-defined operators

Most of the operators in Ruby have behaviour defined by the class of the object they are applied to, when the class implements the operator's method.

Specifically:

  • For unary operators, the operator's method is invoked on the operand e.g. !a is equivalent to a.!()
  • For binary operators, the operator's method is invoked on the left operand with a single argument of the right operand, e.g. a + b is equivalent to a.+(b)

Because operator behaviour is defined by API, not language, I won't try to describe them exhaustively here, but I will give an overview of their behaviour for core classes.

Unary operators

Unary minus

- <value>

Implements <value>.-@()

Standard unary minus operator.

Class Action
Numeric Returns 0 - <value>
Fixnum Returns negation of <value>
Float Returns negation of <value>
Bignum Returns negation of <value>
Complex Returns negation of <value>

Unary plus

+ <value>

Implements <value>.+@()

Standard unary plus operator.

Class Action
Numeric Returns <value>

Unary complement

~ <value>

Implements <value>.~()

For integers, a standard complement operator. For regular expressions, performs a match to the last line.

Class Action
Fixnum Returns one's complement of <value>
Bignum Returns one's complement of <value> (bignums are conceptually of infinite length)
Regexp Returns the default implementation of <value> =~ $_

Unary not

! <value>
not <value>

Implements <value>.!()

A standard logical "not" operator.

Class Action
Object Returns true if <value> is falsy, otherwise false

Binary arithmetic operators

Binary plus

<left-value> + <right-value>

Implements <left-value>.+(<right_value>)

Standard addition operator for Numerics. Standard concatenation operator for Arrays and Strings. Add seconds for Time.

Class Action
Fixnum, Bignum, Rational, Float, Complex Returns the addition of left and right Numeric values. Can add 2 values each of any Numeric class, and it will "upgrade" one of the operands to match the class of the other (in the order listed here)
Array Returns the concatenation of the left and right Array values. Right operand can be implicitly converted with to_ary
String Returns the concatenation of the left and right String values. Right operand can be implicitly converted with to_str
Time Returns a Time object resulting in adding the Numeric right value, in seconds, to the Time object

Binary minus

<left-value> - <right-value>

Implements <left-value>.-(<right_value>)

Standard subtraction operator for Numerics. For Arrays, returns an Array containing the left value's items excluding the right value's items. For Time, subtract another Time and get # seconds, or subtract # seconds and get a Time.

Class Action
Fixnum, Bignum, Rational, Float, Complex Returns the subtraction of left and right Numeric values. Can subtract 2 values each of any Numeric class, and it will "upgrade" one of the operands to match the class of the other (in the order listed here)
Array Returns an Array containing the values in the left operand excluding the values in the right operand. Matching elements are located by converting the right value to a Hash and testing each left value element against it. Right operand can be implicitly converted with to_ary
Time If right value is Numeric, returns a Time object resulting in subtracting that value, in seconds, from the Time object. If right value is Time, returns the number of seconds between the 2 times.

Binary multiply

<left-value> * <right-value>

Implements <left-value>.*(<right_value>)

Standard multiplication operator for Numerics. For Arrays and Strings, concatenates the specified number of copies of the left operand. For arrays can also be used to join elements into a string separated by right operand.

Class Action
Fixnum, Bignum, Rational, Float, Complex Returns the multiplication of left and right Numeric values. Can multiply 2 values each of any Numeric class, and it will "upgrade" one of the operands to match the class of the other (in the order listed here)
Array If right value is a String, will return a String containing each of the Array elements (stringified) separated by the string in the right value. If right value is a Numeric, will return an Array that is the concatenation of that many copies of the left value.
String Right value is a Numeric, returns a String that is the concatenation of that many copies of the left value.

Binary divide

<left-value> / <right-value>

Implements <left-value>./(<right_value>)

Binary modulus

<left-value> % <right-value>

Implements <left-value>.%(<right_value>)

Binary power

<left-value> ** <right-value>

Implements <left-value>.**(<right_value>)

Binary bitwise or

<left-value> | <right-value>

Implements <left-value>.|(<right_value>)

Binary bitwise xor

<left-value> ^ <right-value>

Implements <left-value>.^(<right_value>)

Binary left shift

<left-value> << <right-value>

Implements <left-value>.<<(<right_value>)

Binary right shift

<left-value> >> <right-value>

Implements <left-value>.>>(<right_value>)

Match operators

=~ !~

Comparison operators

<

= <= <=> == != ===

Language-defined operators

The following operators are defined by the language - their behaviour can't be altered.

Simple assignment

<lhs> = <value>
<lhs> = <asgn_value> rescue <resc_value>

Returns: The value assigned

Same as other languages - assigns a value to a variable. <lhs> can be:

  • a local variable
  • an instance variable
  • a class variable
  • a global variable
  • an array reference
  • an attribute reference
  • a constant reference

Note that a local variable in <lhs> which did not previously exist will be created by this expression. That same local variable can actually be used as part of the <value> expression - its value in such usage will be nil.

In some circumstances a rescue modifier may be attached to the assignment expression, whereas normally they can only be applied to whole statements. This results in the value of <asgn_value> rescue <resc_value> being assigned to the variable. See the operator precedence section for rules on when this is permitted.

Multiple right-hand-side assignment

<lhs> = <mrhs>

Returns: the value of <mrhs> as encapsulated in an Array

<mrhs> is a comma-separated list of regular and splat call arguments, and is processed similar to a method call. The resulting list of values is put in an Array, and this value is assigned to the <lhs>. In other words, this syntax is equivalent to assigning an array literal with <lhs> = [ <mrhs> ] except that the <mrhs> syntax does not permit hash associations or trailing commas.

Multiple left-hand-side assignment

<mlhs> = <value>
<mlhs> = <mrhs>

Returns: the value of <value> or <mrhs>

Multiple comma-separated variable references are provided in <mlhs>, and the value to be assigned is treated as an Array, its contents assigned to variable references in <mlhs>. Any variable references permitted in a simple assignments are also permitted in multiple left-hand-side assignments.

<mlhs> is a comma-separated list of at least 1 of the following elements, in order:

  • 0 or more nested "mandatory" variable references, which may be (in any order):
  • Simple variable references
  • Nested multiple left-hand-side structures enclosed in ( and )
  • An optional "rest" variable reference, which may be:
  • A regular "rest" variable reference, which is written as a variable reference prefixed by * (e.g. *rest)
  • An anonymous "rest" reference, which is written as *
  • If a "rest" variable reference is present
  • 0 or more "post" variable references, which have the same form as nested "mandatory" variable references

Nested multiple left-hand-side structures have the same form as <mlhs>.

Some additional syntax rules apply:

  • If <mlhs> or a nested multiple left-hand-side structure contains only "mandatory" references, it may be followed by a trailing ,
  • If there is only a single "mandatory" reference, it MUST be followed by a trailing ,
  • <mlhs> (excluding nested structures) may optionally be enclosed in ( and ), without changing the meaning of the expression (i.e. without it being considered a nested expression). Note that this means that a, b = c and (a, b) = c mean the same thing, however ((a, b)) = c is different.

A value is assigned to <mlhs> as follows:

  • Convert the value to Array if necessary:
  • If the value is already an Array or an instance of a subclass of Array, OK
  • Otherwise, conversion to an Array is attempted by calling the to_ary method on the value
    • If the to_ary method call fails to resolve or returns nil, the conversion has failed
    • If the returned object from to_ary is not a subclass of Array, an exception is thrown
    • Otherwise, OK
  • If the conversion failed (without throwing an exception), then the conversion is effected by creating a new Array of 1 element, which is the value
  • The contents of the array are assigned to:
  • "mandatory" variable references, from left to right, then
  • if a "rest" reference is present, any values in the array in excess of the number of mandatory/post references are selected and:
    • if the "rest" reference is anonymous, ignored
    • otherwise, compiled into an array and assigned to the "rest" reference
  • "post" variable references, from left to right
  • if there are fewer elements in the array than there are mandatory/post variable references, the unset references are set to nil
  • Any remaining values in the array are ignored
  • If there is a regular "rest" variable reference and no array elements to assign to it, it is assigned an empty array
  • If there are nested multiple left-hand-side structures in mandatory or post, this array conversion and assignment process is repeated for them

Some examples:

a, b = [1, 2, 3] # a = 1, b = 2
a, *, (b, c) = [1, 2, 3, [4, 5]] # a = 1, b = 4, c = 5

Complex assignment - binary arithmetic operators

<lhs> <op>= <value>

Returns: the value of after the expression is processed

There are complex assignment forms for all the binary arithmetic operators, formed by writing an = on the end of the operator e.g. +=. For these operators, an expression a <op>= b is equivalent to a = a <op> b, e.g. a += b is equivalent to a = a + b.

All complex assignments may not have multiple left- or right-hand-sides, and they may not have explicitly-scoped constant references on their left-hand-side.

Complex assignment - logical operators

<lhs> &&= <value>
<lhs> ||= <value>

Returns: the value of after the expression is processed

Mostly, these are similar to other complex assignment operators (i.e. a &&= b is like a = a && b), except:

  • If the expression is short circuited and <value> is never evaluated, then the assignment is not performed at all, which has implications for assignments that may have side effects, especially:
  • global variable assignments may cause trace procedures to be invoked
  • constant assignments may assign to a different constant than the one that is accessed
  • attribute and array reference assignments invoke methods to perform the assignment, which may have whatever side effects they like
  • In some cases, the ||= expression has the ability to determine that <lhs> is falsy when it's not defined at all - so it assigns to the <lhs> when other complex assignments would throw an exception

Specific behaviour for a &&= b:

  • If a is other than a local variable, equivalent to a && (a = b), i.e. doesn't assign if a is falsy
  • If a is a local variable, it is equivalent to a && (a = b) except that if a was not defined before the expression, a &&= b defines it and therefore doesn't cause a syntax error (whereas a && (a = b) would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent to a = a && b even though an assignment is not performed if a is falsy

Specific behaviour for a ||= b:

  • If a is an array or attribute reference, equivalent to a || (a = b), i.e. doesn't assign if a is truthy
  • If a is a local variable, it is equivalent to a || (a = b) except that if a was not defined before the expression, a ||= b defines it and therefore doesn't cause a syntax error (whereas a || (a = b) would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent to a = a || b even though an assignment is not performed if a is truthy
  • If a is a global variable, instance variable, class variable, or (implicitly namespaced) constant, it will also test to see if the referenced variable is defined and, if not, perform the assignment i.e. it's equivalent to (defined? a && a) || (a = b). For undefined global variables, this appears to make no difference as they return nil anyway, but for the others it prevents an exception being thrown due to the variables being undefined

Logical operators

Logical and

<value> && <value>
<value> and <value>

Returns: the left value, if falsy, otherwise the right value

Similar to other languages, in that it returns a truthy value if both operands are truthy, and a falsy value otherwise. Uses short-circuit evaluation - the second operand is not evaluated if the first is falsy. Typically used to test whether two values are both truthy, however can also be used to evaluate the second value on the condition that the first one is truthy.

a && b therefore behaves like:

(
  tmp = a
  if tmp
    b
  else
    tmp
  end
)

Both &&, and forms of the operator have the same meaning, however they have different positions in the syntax grammar and precedence.

Logical or

<value> || <value>
<value> or <value>

Returns: the left value, if truthy, otherwise the right value

Similar to other languages, in that returns a truthy value if one of the operands is truthy, and a falsy value otherwise. Uses short-circuit evaluation - the second operand is not evaluated if the first is truthy. Typically used to test whether at least one of two values is truthy, however can also be used to evaluate the second value on the condition that the first one is falsy.

a || b therefore behaves like:

(
  tmp = a
  unless tmp
    b
  else
    tmp
  end
)

Both ||, or forms of the operator have the same meaning, however they have different positions in the syntax grammar and precedence.

Ternary ?:

<cond> ? <value-if-true> : <value-if-false>

Returns: see if expression

Standard ternary operator - equivalent to if <cond> then <value-if-true> else <value-if-false> end, including the special condition processing that applies to if expressions

defined?

defined? <expr>

Returns: nil if undefined, otherwise a String (see below)

The defined? operator is used to test whether <expr> is defined. <expr> is not evaluated, its structure is examined at runtime to determine whether it is defined. If not, it returns nil, otherwise it returns a String. The behaviour of defined? based on the <expr> that is passed to it is described below:

<expr> Is defined if Return value
nil Always "nil"
self Always "self"
true Always "true"
false Always "false"
An array literal If contains other than simple (non splat/hash) elements, or every element within is defined? (short-circuiting on the first failure) "expression"
A local variable Always (a local variable which is not defined is a method call) "local-variable"
An instance variable The variable is found "instance-variable"
A class variable The variable is found "class-variable"
$&, $` , $', $", backreferences The variable is not nil "global-variable"
An other global variable The variable exists (except for where it has been referenced but never assigned to) "global-variable"
An implicitly-namespaced constant The constant is defined according to constant-defined logic "constant"
An explicitly-namespaced constant The namespace is defined? and (after evaluating it) the constant is defined on it according to constant-defined logic "constant"
Any type of method call with a block Always "expression"
An implicitly-targeted method call If the method is defined for self, it isn't flagged "not implemented", and if all its arguments are mandatory, they are all defined (short-circuiting on the first failure) "method"
An array reference on self As above for the array reference method "method"
A simple assignment to an array/attribute reference on self As above for the array/attribute set method "method"
An explicitly-targeted method call If the target is defined and, after evaluating it, the method is defined for it and its visibility makes it accessible from the current scope, or respond_to_missing?(:method, false) on the target indicates it responds, and if all its arguments are mandatory, they are all defined (short-circuiting on the first failure) "method"
An array reference on other than self As above for the array reference method "method"
A simple assignment to an array/attribute reference on other than self As above for the array/attribute set method "method"
A class-defined operator expression As above for the operator method "method"
A yield expression If there is a block passed to the current method "yield"
A super expression If the super call would resolve according to normal logic, except no method_missing, "not implemented" methods are counted undefined, and buggy interactions with define_method "super"
Any other assignment expression Always "assignment"
Any other expression Always "expression"

Note that in some circumstances defined? evaluates part of its expression. If this evaluation raises an exception, defined? will catch this exception and return nil.

Double-dot and triple-dot

<from-value> .. <to-value>
<from-value> ... <to-value>

These operators serve 2 different functions.

Range constructors

Returns: a Range object

These operators are normally Range constructors, and are equivalent to the default implementations of:

  • For .., Range.new(<from-value>, <to-value>, false)
  • For ..., Range.new(<from-value>, <to-value>, true)

Flip-flops

Returns: the flip-flop's state

If these operators appear (in some circumstances) within the conditional expression of if, elsif, unless, while, until, ?:, not and !, they are flip-flops, not range constructors.

Flip-flops create a special hidden variable in the local variable context, which is used to keep track of whether the flip-flop is "on" or "off" - it is this state which is returned as the value of the flip-flop. A flip-flop starts its life "off", and will be "on" from the point when the <from-value> expression is truthy up to and including the point where the <to-value> is truthy. The difference between the two forms is that for ..., the <to-value> expression will not be evaluated when the <from-value> first becomes truthy i.e. if both values become truthy at the same time, this will not cause the flip-flop to be switched "off" after once being reported "on".

If either value is an Fixnum integer literal, it is equivalent to comparing via == to $. i.e. 3 is equivalent to 3 == $..

A flip-flop is not a Ruby value and can't be manipulated as such - they are purely a feature of conditional expressions.

Here is some Ruby code describing the behaviour of flip-flops, assuming tmp is a local context variable.

changed = false

unless tmp # flip-flop is off
  if from_value
    tmp = true
    changed = true
  end
end

if tmp && !(changed && operator == "...") # flip-flop is on, but don't try to switch off if it was just switched on and operator is "..."
  if to_value
    tmp = false
    changed = true
  end
end

(!tmp && changed) ? true : tmp # report "on" up to and including the moment it's switched "off"

Operator precedence

Broadly, the following table describes the precedence of Ruby operators.

No|Type/Associativity|Description|Operators ---|---|---|---|--- 1|Binary left|Reference|. :: 2|Unary|General|! ~ + 3|Binary right|Power|** 4|Unary|Minus|- 5|Binary left|Products|* / % 6|Binary left|Additions|+ - 7|Binary left|Shifts|<< >> 8|Binary left|Bitwise and|& 9|Binary left|Bitwise or|| ^ 10|Binary left (TODO why?)|Directional comparisons|> >= < <= 11|Binary none|General comparisons|<=> == === != =~ !~ 12|Binary left|Logical and, argument form|&& 13|Binary left|Logical or, argument form||| 14|Binary left|Range constructors|.. ... 15|Ternary right|Logical ternary|?: 16|Unary|Defined|defined? 17|Binary none|Assignment modifier|rescue 18|Binary right|Assignments|= += -= etc. 19|Unary|Logical not, expression form|not 20|Binary left|Logical and/or, expression form|and or 21|Binary left|Statement modifiers|if unless while until rescue

Some special rules also apply:

  • If a rescue modifier is applied to an assignment, e.g. <lhs> = <rhs> rescue <value>, rescue becomes an assignment modifier and takes precedence over the assignment operator (i.e. it will be interpreted as <lhs> = ( <rhs> rescue <value> )) according to the following rules:
  • The assignment operator may be simple or complex
  • This does not apply to an assignment with a multiple left-hand-side, a multiple right-hand-side or a command call on the right-hand-side
  • If the argument to defined? or not? is within parentheses and there is no whitespace preceding it, then the operator has the highest precedence (higher even than . and ::)

Clone this wiki locally