Skip to content

Ruby Expressions

datanorris edited this page Jan 26, 2016 · 9 revisions

Alias - method

alias new_method old_method
alias :new_method :old_method

Returns: nil

alias creates an alias new_method of method old_method in the class base context. Any valid method names are permitted. Both methods may be expressed as a raw method name or as any form of symbol literal.

Alias - global variable

alias $new_gvar $old_gvar

Returns: nil

alias creates an alias $new_gvar of global variable $old_gvar. Any valid global variable names are permitted, except you may not create aliases of numbered backreferences e.g. $1.

Undef - method

undef method_name, :method_name2, ...

Returns: nil

undef marks 1 or more methods as undefined in the class base context. Any valid method names are permitted. Methods may be expressed as raw method names or as any form of symbol literals.

If/unless

if <cond> then <stmts> [ elsif <cond> then <stmts> ... ] [ else <stmts> ] end
if <cond>; <stmts> [ elsif <cond>; <stmts> ... ] [ else <stmts> ] end
if <cond>
  <stmts>
[ elsif <cond>
  <stmts> ... ]
[ else <stmts> ]
end

unless <cond> then <stmts> [ else <stmts> ] end

<stmt> if <cond>
<stmt> unless <cond>

Returns: The value of the statements block that was executed by the expression, or if none was, nil

Standard if-then-else statement. The then keyword is optional if it is preceded by a semicolon or a newline. unless is the reverse of if - it succeeds if the condition is falsy.

If/unless statement modifiers cause their statement to be executed only if their test succeeds.

Condition expressions

The condition expression in if/unless expressions, and in some other expressions, is processed with some special rules.

  • If the condition is composed of any of the following structures, it will apply these rules to each subcomponent of them:
  • Logical operations i.e. and, or, && and ||
  • Grouped expressions or begin/end expressions i.e. (...) or begin ... end
  • If the condition is a multiple left-hand-side assignment e.g. a, b = true, false, this is a compile time error - there is no technical reason this must be the case but apparently Ruby creators find the idea of a multiple assignment in a conditional expression to be so particularly unintuitive and confusing that they've disallowed it
  • If the condition is an assignment of a literal, Ruby may warn you that perhaps you meant ==, not =
  • If the condition is a regular expression literal, this is treated as syntax shorthand for a match to $_ i.e. /hello/ is treated as /hello/ =~ $_ except that no named capture assignment is performed
  • If the condition is some other type of literal, it may issue a warning
  • If the condition is a range literal, it is treated as a flip-flop

While/until

while <cond> do <stmts> end
while <cond>; <stmts> end
while <cond>
  <stmts>
end

until <cond> do <stmts> end

<stmt> while <cond>
<stmt> until <cond>

begin <stmts> end while <cond>
begin <stmts> end until <cond>

Returns: nil if the loop terminated normally, however the loop can be explicitly terminated with a return value via a break expression

Standard while loop. The do keyword is optional if it is preceded by a semicolon or a newline. until is the reverse of while - it succeeds if the condition's value is falsy.

While/until modifiers cause their statement to be repeatedly executed as long as their test succeeds.

A while/until modifier on a begin ... end expression is a slightly different special case - Ruby will execute the begin ... end statement once before it tests the condition and proceeds with the loop. In all other forms, Ruby will test the condition first.

The condition is processed according to the condition expression special rules described above.

Case

case [ <expr> ] when <whenargs> then <stmts> [ when <whenargs> then <stmts> ] [ else <stmts> ] end
case [ <expr> ]; when <whenargs>; <stmts> [ when <whenargs>; <stmts> ] [ else <stmts> ] end
case [ <expr> ]
  when <whenargs>
    <stmts>
  [ when <whenargs>
    <stmts> ]
  [ else <stmts> ]
end

Returns: The value of the statements block that was executed by the expression, or if none was, nil

A reasonably standard switch/case expression. Searches for the first successfully matching when clause, and executes the statements associated with it. If no when clause matches, executes the else clause.

The arguments to the when clause are a comma-separated list of regular and splat arguments, which are processed similar to method call arguments. After splatting, the case statement will test each argument in the list and if any one of them matches, then the when clause has matched. Arguments are tested as follows:

  • If no <expr> was provided, test succeeds if argument is truthy
  • Otherwise, test succeeds if matches <expr> value via a the case equality operator i.e. <arg> === <expr>

The first when clause may be optionally preceded by a semicolon, and the then keyword is optional if it is preceded by a semicolon or newline.

For

for <lhs> in <expr> do <stmts> end
for <lhs> in <expr>; <stmts> end
for <lhs> in <expr>
  <stmts>
end

Returns: The value returned by the each method call

Reasonably standard for each ... in ... statement. The do keyword is optional if it is preceded by a semicolon or a newline.

Basically, the for expression iterates over <expr> by calling the each method on it and passing <stmts> as the block. The <lhs> is almost, but not quite, processed as though it is the formal arguments in said block - <lhs> can actually be any valid left hand side target including multiple-assignment targets.

Specifically, the for statement is a syntax shorthand for certain forms of each method calls, with one difference - the for statement does not push a new dynamic variable context, so local variables created inside a for statement can be accessed outside of it. The block passed into the each method has a single hidden argument, which is not named and not accessible from Ruby code - we'll call it intarg in the explanations below.

<lhs> can be any simple assignment target (including variables, constants, array references and attributes), and it can also be a multiple-left-hand-side assignment target (i.e. a comma-separated list of lhs targets, rest targets, and nested multiple targets).

If <lhs> is a simple assignment target, the for expression effectively translates to:

<expr>.each { |intarg|
  <lhs> = intarg
  <stmts>
}

If <lhs> is a multiple target (including if it's a single rest arg), the for expression will implement an autosplat rule similar to (not quite the same as) that involved in block calls. The for expression effectively translates to:

<expr>.each { |*intarg|
  if intarg.length == 1 and ingarg[0].kind_of? Array
    intarg = intarg[0]
  end
  <lhs> = intarg
  <stmts>
}

Class

Normal class form:

class <classname> [ < <superclass> ]; <stmts> end
class <classname> [ < <superclass> ]
  <stmts>
end

Singleton class form:

class << <obj>; <stmts> end
class << <obj>
  <stmts>
end

Returns: The value of <stmts>

The class expression is the normal way to define a Ruby class. It creates a class (or accesses an existing class) and sets up the execution context so that code in <stmts> operates on that class, in particular def, class, module statements and certain API methods.

The normal class form:

  • <classname> may be any constant reference
  • <superclass> may be any expression resolving to a Class object. If absent, the Object class is the default.
  • Looks for the specified <classname> constant
  • If not found, creates a new class with the given <superclass> and sets it to the <classname> constant
  • If found, and it's not a class, raise an exception
  • If found, and its superclass (excluding singletons/mixins) doesn't match the given <superclass>, raise an exception
  • Makes self the class, pushes the class onto the class reference stack, and executes <stmts>

The singleton class form:

  • <obj> may be any expression
  • Accesses the singleton class of obj
  • Makes self the class, pushes the class onto the class reference stack, and executes <stmts>

The singleton class form may be used anywhere in Ruby code. The normal class form may not be used inside a def expression, unless it is further nested inside a class singleton form expression.

Module

module <modulename> <stmts> end

Returns: The value of <stmts>

The module expression is the normal way to define a Ruby module. It creates a module (or accesses an existing module) and sets up the execution context so that code in <stmts> operates on that module, in particular def, class, module statements and certain API methods.

  • <modulename> may be any constant reference
  • Looks for the specified <modulename> constant
  • If not found, creates a new module and sets it to the <classname> constant
  • If found, and it's not a module, raise an exception
  • Makes self the module, pushes the module onto the class reference stack, and executes <stmts> reference stack, and executes <stmts>

module may not be used inside a def expression, unless it is further nested inside a class singleton form expression.

Def

Normal form:

def <methodname> <formalargs> <stmts> end

Singleton method form:

def <obj>.<methodname> <formalargs> <stmts> end
def <obj>::<methodname> <formalargs> <stmts> end

Returns: nil

Normal form defines (or replaces) a method on the class base context class, and sets visibility as per the current visibility mode of the class base context.

Singleton method form defines a public method on the singleton class of <obj>.

Formal arguments must either be surrounded by ( and ) or followed by a ; or newline.

Rescue

Block clause form:

<stmts>
[ rescue [ <exc_list> ] [ => <exvar> ] then <stmts> ]
[ rescue ... ]
[ else <stmts> ]

Modifier form:

<stmt> rescue <stmt>

Returns: the value of the executed rescue or else block, if executed, otherwise the value of the initial <stmts>

Reasonably standard exception catching clause. The then keyword is optional if it is preceded by a semicolon or a newline.

Rules for block clause form:

  • These rescue/else clauses are only permitted at the end of the statement body of class, module, def and begin/end expressions, but they must come before any ensure clause. The initial <stmts> here is the whole statement body within those expressions
  • If an exception is thrown in <stmts>, each of the rescue clauses is tested for a match, and the first match found is executed
  • The rescue clauses function exactly like when clauses would function in a case statement matching to the raised exception:
  • <exc_list> is a comma-separated list of regular and splat arguments, which are processed similar to method call arguments. <exc_list> is optional and if not provided, defaults to the StandardError class
  • If any one of the list matches the raised exception via case equality i.e. <arg> === <exception>, the rescue clause matches
  • Typically, arguments to rescue are classes, and === will (by default) match an exception object which is an instance of a subclass of that class
  • <exvar> is any valid reference permitted on the left-hand-side of a non-multiple assignment. If a rescue clause matches, the exception that is caught will be assigned to this reference.
  • If no exception is raised in <stmts>, the else clause is executed

The modifier form works the same way as the block clause form, except that no <exc_list> or <exvar> can be provided (so it can only catch instances of StandardError), only one rescue clause can be provided, and no else clause can be provided. Multiple rescue modifiers can be chained though, as with other modifiers. The following are equivalent:

<stmt1> rescue <stmt2> rescue <stmt3>

begin
  begin
    <stmt1>
  rescue
    <stmt2>
  end
rescue
  <stmt3>
end

Ensure

<block_stmts>
ensure
<ensure_stmts>

Returns: the value of <block_stmts>

Reasonably standard "finally" clause. <ensure_stmts> will be executed after <block_stmts>, even if <block_stmts> is terminated early due to a control transfer:

  • If <block_stmts> completes normally, <ensure_stmts> is executed afterwards
  • If <block_stmts> does not complete normally due to some form of control transfer (e.g. it raises an exception, uses the throw method, or uses a control transfer expression), <ensure_stmts> will be executed and then execution will continue as it would have if the ensure clause was not there
  • Note that in this case, the ensure block functions similar to a catch-all rescue clause (except it catches all control transfers, not just exceptions), and then at the end of <ensure_stmts>, it re-raises the caught exception/control transfer. Therefore, if <ensure_stmts> itself causes a second control transfer and does not complete normally, then the original control transfer will not be re-raised.

The ensure clause is only permitted at the end of the statement body of class, module, def and begin/end expressions. The <block_stmts> here is the whole statement body within those expressions, including rescue clauses.

BEGIN{}

BEGIN { <stmts> }

Returns: N/A

The <stmts> inside a BEGIN{} block will be executed before any other code in the source file. If there are multiple BEGIN{} blocks, they will be executed in the order they occur. If there is a BEGIN{} block inside another BEGIN{} block, the inner BEGIN{} block will be executed before any other code in the outer BEGIN{} block.

BEGIN{} is more of a declarative statement than an expression really, because it's fully processed at compile time - the only such example in all of Ruby.

BEGIN{} blocks may not be nested within any other code structures except for other BEGIN{} blocks.

What is the point, you may ask, when you can just put the code at the top of the source file instead? I'm not really sure. Maybe it's intended to be used for some scenario where a Ruby sourcefile is spliced together from multiple sources.

END{}

END { <stmts> }

Returns: nil

The <stmts> inside a END{} block will be executed just prior to the running Ruby VM terminating. It does this by, when the END{} block is executed at runtime, creating a Proc out of the <stmts> and flagging the Proc to be called when the Ruby VM terminates. END{} blocks are only processed the first time they are executed at runtime - subsequent executions of the same END{} block do nothing.

In the case of multiple END{} blocks, they will be executed in reverse of the order that they were processed.

Begin/end

begin <stmts> end

Returns: the value of <stmts>

begin/end is perhaps more of a grammatical statement grouping syntax, like ( <smts> ), than an expression. However, while ( <stmts> ) has no meaning other than to group together some statements and put them in a place in Ruby code they might not normally be allowed, begin/end has a bit of meaning:

  • Its statement body supports the rescue and ensure clauses
  • It has special meaning for the until and while modifiers

Super

super [ <args> ] [ <block> ]

Returns: the value of the superclass method call

super is used to invoke the superclass's version of the currently executing method. It has two basic forms:

  • implicit arguments, where <args> is not provided and the superclass's method is called with the arguments used to invoke the currently executing method (or in some cases the current value of those formal argument variables)
  • explicit arguments, where <args> is provided and the superclass's method is called with those arguments. Note that if you want to explicitly specify no arguments, you can use super()

In all cases, if neither <block> nor a block call argument is provided, super will pass the block that was passed to the currently executing method (if any). If you wish to explicitly specify no block, give it a &nil block argument.

First the "method context" of the super call must be discovered, i.e. the method whose superclass implementation needs to be called. The way Ruby does this is a bit confusing, inconsistent and buggy, but the intention is probably as follows:

  • A super call's method context can be defined by:
  • Its local variable context, if that context is a def expression (i.e. basic method definition)
  • Any of the contexts in its dynamic variable context stack, potentially, if the associated blocks were made into methods via the define_method API
  • Method context includes the name of the running method, and the class/module in which the running method's definition was found
  • Method aliasing does not impact this - if a method alias is called, and the method contains a super call, super will invoke the superclass's version of the original method name, not the alias name
  • A variable context defines the method context only if it was created (i.e. when the code was invoked that created the context which the super call can now see) as a method call
  • i.e. dynamic variable contexts created by blocks invoked by yield or call don't have associated method contexts - they only have method contexts if they are invoked by a method call when they've been define_method'd
  • The method context of a super call is the innermost method context on the dynamic variable context stack or, if none, the local variable context's method context
  • If no method context is found, super will raise an error.
  • Note that a Proc closure can contain a super call, and the closure will remember its method context

Next, super will locate the superclass implementation of the method context:

  • Identify a class/module to start searching from
  • If the method is implemented on a class, start with its superclass
  • If the method is implemented on a module, locate the reference to the module in self's class's ancestors, and start with the superclass of that reference
    • For some reason, if the module reference is not found, it will start with Object.
  • If the start class is not part of self's inheritance tree, an error will be raised - it seems this can only happen due to Ruby bugs or due to self being deliberately altered, e.g. by invoking super in a Proc which is invoked via instance_eval.
  • super will locate the method via standard method search logic, beginning with the start class, and invoke it on the self object:
  • It will search the start class and each of its ancestor classes in turn for the method
  • super can access methods of any visibility
  • If the method is not found or it's marked undefined, self.method_missing will be invoked (unless method_missing is the method context we're trying to super)

Note that the odd Object clause above means you can invoke super with a self incompatible with the method context (e.g. by changing self through instance_eval), as long as the method context is a module method and the new self is an instance of Object.

If explicit arguments are provided, they take the same form as method call arguments and are processed the same way. They may be provided in parentheses (method call style) or without parentheses (command call style).

If arguments are implicit, then the superclass method will be called with arguments very similar to the method context's call arguments, except that in general the current value of formal argument variables at the time of the super invocation is what gets provided to the superclass method. Specifically, the superclass's method will be called with call arguments as follows:

  • The current value of each mandatory formal argument is provided as a call argument
  • The current value of each optional formal argument is provided as a call argument
  • The current value of the rest formal argument is provided as a splat call argument
  • The current value of each post formal argument is provided as a call argument
  • The exception to the above rules is for any multiple-assignment arguments in mandatory or post - for each multiple-assignment argument, the original simple-form call argument that the method context was called with (i.e. prior to the argument begin assigned to the multiple-assignment) is provided as a call argument
  • Note that implicit super calls in method contexts which were created via define_method are not yet supported in Ruby 1.9.3

Yield

yield [ <args> ]

Returns: the value returned by invoking the block

yield invokes the block that was passed in to the method, as stored in its local variable context, with the arguments provided. Arguments may be provided in parentheses (method call style) or without parentheses (command call style).

yield cannot be used to pass a block as a call argument. Nor can it be used to invoke the block passed to the currently executing block (because yield invokes the block stored in the local variable context, i.e. the block passed to the method). If you wish to invoke a block passed to the current block, capture the passed block in a block formal argument and use the call method. If you wish to pass a block as a call argument to a block, do so using the call method.

Return

return [ <args> ]

Returns: N/A

Similar to a typical return statement, return is used to immediately exit from the currently running method or lambda block.

Arguments to return must be in command call style, i.e. provided without enclosing parentheses. They take the same form as method call arguments, except that you may not pass a block or block argument to return. The arguments are used to determine the return value of the method call/lambda block being exited, as follows:

  • If a single, non-splatted argument is provided, its value is the return value
  • If a set of hash associations is provided (and nothing else), the return value is a hash composed of them
  • Otherwise, the return value is an array containing the list of arguments (after splatting etc.)
  • If no args are provided, it returns nil

The implementation of return is again somewhat buggy, but the intention appears to be an echo of the logic for super, as follows:

  • A return statement has the potential to return from any def unit or lambda block it is lexically enclosed by, limited only by an enclosing def. It can only return from the specific invocation of said def/blocks which created the unit of code containing the return - not just any invocation. Specifically, it can return from:
  • A block invocation whose corresponding dynamic variable context is accessible by the return
  • A def method invocation whose corresponding local variable context is accessible by the return
  • A block or def invocation whose variable context is accessible by the context which invoked a class or module expression whose local variable context is accessible by the return. This is applied transitively in the case of multiple nested class/module expressions
    • In other words, a return expression can cross a class/module expression to find what it's returning from - class/module is treated more like a dynamic context than a local context
  • return will return from the innermost of the eligible invocations that is still currently running. This can involve returning from multiple intermediate running methods/blocks as well e.g. a Proc may contain a return statement that returns from a method 4 steps up the program stack
  • If nothing is found which can be returned from (e.g. return is called from a Proc closure created inside a method which is no longer running), an error is raised

Probably the most important bug is that you can't return from a lambda block which was invoked with the yield statement - this is fixed from Ruby 2.1.

Break

break [ <args> ]

Returns: N/A

The break expression can be used as follows:

  • If in the context of a lambda block, terminate that block, the same as return
  • If in the context of a non-lambda block, terminate the method call which this block is lexically a part of
  • If in the context of a while/until/for loop, terminate the loop

Arguments to break determine the return value of the expression that break is terminating, and have the same rules as the arguments to return.

The target of a break expression will be the innermost while/until/for loop or block lexically enclosing the break expression, limited only by an enclosing def. It is selected at compile time, and if not found causes a compile time error. The target is then processed as follows:

  • If a loop, the loop is terminated
  • If a lambda block, the block is terminated
  • If a non-lambda block, look at the invocation of the lexical parent context of the block i.e. the parent dynamic or local variable context of the block
  • If the parent context is not currently running, raise an error
  • If the parent context is currently running but it's not currently within the method call to which the block was passed, raise an error
  • Otherwise (i.e. the parent context is currently in the method call to which the block was passed), terminate the method call. Note that this form will therefore terminate the block, its lexical parent method call, and in the case of Proc closures potentially multiple intermediate method/block invocations as well.

There are some limitations on break:

  • It may not pass a singleton class expression - if a singleton class expression is nested between the break expression and its target, the target is not found and a compile time error is raised
  • It may not pass an "eval" boundary - you may not use an eval method to compile and run code containing a break expression in order to break out of a target outside that code

Next

next [ <args> ]

Returns: N/A

next has the same rules as break except for how it processes its target:

  • If a while/until loop, the body of the loop is skipped and execution resumes with evaluating the loop's condition. The arguments passed to next have no effect.
  • If a for loop, the body of the loop is skipped an execution resumes with the for loop's next iteration. The arguments passed to next are the value of the for body which is returned to its iterator.
  • If a block (lambda or not), the block is terminated. The arguments are the value of the block.

Redo

keyword_redo

Returns: N/A

redo has the same rules as break except that it accepts no arguments and for how it processes its target:

  • If a while/until/for loop, execution restarts at the start of the body of the loop.
  • If a block (lambda or not), execution restarts at the start of the block

Retry

retry

retry may be used in a rescue clause, in order to restart execution of the statement block which was covered by that rescue clause.

retry may not be further nested inside:

  • Blocks/lambdas
  • def, class or method expressions
  • "Eval" boundaries (i.e. you may not invoke eval 'retry' or suchlike to compile code containing a retry which attempts to target a statement block outside of the compiled code

Comments

Comments in Ruby take two forms:

  • When a # is encountered, the rest of the line is a comment
  • When the beginning of a line is =begin (followed by whitespace or end of line), all further text is a comment until a line is reached which begins in =end (followed by whitespace or end of line)

Magic comments

Magic comments are Ruby # comments which include special syntax to declare the encoding of the source file or change other parameters. They take the following form of comments which contain somewhere within a set of name-value pairs as follows:

-*- [ <name> : <value> ] [ ; ] [ <name> : <value> ] ... -*-

<value> may be:

  • Any string of characters excluding whitespace, " and ;
  • Any string of characters within double-quotes - all backslash-escaped characters are interpreted as themselves

is case insensitive.

Recognised names include:

  • encoding, coding: declares <value> to be the encoding of the source file. This magic comment may only appear in a comment on the first line of the source file (excluding any #! shebang line if present), and the comment must be preceded by nothing but whitespace
  • warn-indent, warn_indent: <value> is case-insensitive and either true or false, to enable or disable issuing warnings about code indenting mismatches

Encoding magic comments are also permitted in a looser form, possibly for backward compatibility - they will be interpreted even if the comment doesn't contain full magic comment syntax, but only contains somewhere (insensitively) coding : <value> or coding = <value>.

Special variables

The following special variables exist:

  • __ENCODING__ is the character encoding of the current ruby source file
  • __FILE__ is the name of the current ruby source file
  • __LINE__ is the line number of the current line of code in the source file
  • self is value of self

Embedded data

__END__

TODO

Clone this wiki locally