-
Notifications
You must be signed in to change notification settings - Fork 1
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.
!ais equivalent toa.!() - For binary operators, the operator's method is invoked on the left operand with a single argument of the right operand, e.g.
a + bis equivalent toa.+(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.
- <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>
|
+ <value>
Implements <value>.+@()
Standard unary plus operator.
| Class | Action |
|---|---|
| Numeric | Returns <value>
|
~ <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> =~ $_
|
! <value>
not <value>
Implements <value>.!()
A standard logical "not" operator.
| Class | Action |
|---|---|
| Object | Returns true if <value> is falsy, otherwise false |
<left-value> + <right-value>
Implements <left-value>.+(<right_value>)
<left-value> - <right-value>
Implements <left-value>.-(<right_value>)
<left-value> * <right-value>
Implements <left-value>.*(<right_value>)
<left-value> / <right-value>
Implements <left-value>./(<right_value>)
<left-value> % <right-value>
Implements <left-value>.%(<right_value>)
<left-value> ** <right-value>
Implements <left-value>.**(<right_value>)
<left-value> | <right-value>
Implements <left-value>.|(<right_value>)
<left-value> ^ <right-value>
Implements <left-value>.^(<right_value>)
<left-value> << <right-value>
Implements <left-value>.<<(<right_value>)
<left-value> >> <right-value>
Implements <left-value>.>>(<right_value>)
=~ !~
<
= <= <=> == != ===
The following operators are defined by the language - their behaviour can't be altered.
<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.
<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.
<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 thata, b = cand(a, b) = cmean the same thing, however((a, b)) = cis 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_arymethod on the value- If the
to_arymethod call fails to resolve or returnsnil, the conversion has failed - If the returned object from
to_aryis not a subclass of Array, an exception is thrown - Otherwise, OK
- If the
- 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
<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.
<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
ais other than a local variable, equivalent toa && (a = b), i.e. doesn't assign ifais falsy - If
ais a local variable, it is equivalent toa && (a = b)except that ifawas not defined before the expression,a &&= bdefines it and therefore doesn't cause a syntax error (whereasa && (a = b)would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent toa = a && beven though an assignment is not performed ifais falsy
Specific behaviour for a ||= b:
- If
ais an array or attribute reference, equivalent toa || (a = b), i.e. doesn't assign ifais truthy - If
ais a local variable, it is equivalent toa || (a = b)except that ifawas not defined before the expression,a ||= bdefines it and therefore doesn't cause a syntax error (whereasa || (a = b)would cause a syntax error). Since assigning to local variables has no side effects, you might say this form is equivalent toa = a || beven though an assignment is not performed ifais truthy - If
ais 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 returnnilanyway, but for the others it prevents an exception being thrown due to the variables being undefined
<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.
<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.
<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? <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.
<from-value> .. <to-value>
<from-value> ... <to-value>
These operators serve 2 different functions.
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)
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"
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>,rescuebecomes 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?ornot?is within parentheses and there is no whitespace preceding it, then the operator has the highest precedence (higher even than.and::)
A Ruby Language Reference
Copyright © by Michael Hore, 2016.
Introduction to This Document
Ruby Elements
- Classes and Modules
- Methods
- Blocks, Procs and Lambdas
- Execution Context and Closures
- Variables, Constants and Namespaces
- Types and Literals
- Ruby Expressions
- Operators
Syntax Grammar
Exceptions and Throw
Ruby Sourcefiles and Libraries
Multi Threading
Execution and Lifecycle