Skip to content

Blocks, Procs and Lambdas

datanorris edited this page Jan 5, 2016 · 6 revisions

In Ruby syntax, there are 3 basic types of "units of code" i.e. chunks of code that are executed top to bottom when they are invoked.

  • The whole Ruby program, i.e. usually the whole contents of a Ruby source file, executed when it is loaded. This definition also applies to any Ruby source files that are loaded (e.g. via the require method) as part of another Ruby program.
  • Methods, executed when the method is called
  • Blocks, executed when the block is called

A block is a bit like an anonymous method - it's a chunk of code which accepts method-like arguments and returns a value, but it doesn't have a name and is not defined on a class. However, there are some important differences.

Key characteristics of blocks:

  • A block is not a Ruby value - it's not an Object, it can't be assigned to a variable, can't be used as an operand for operators, can't be passed to methods as a regular value argument etc. Rather, a block is intrinsically a part of the syntax and mechanics of a method call - a block is passed to a method in a call, and the method can execute the block if and as many times as it chooses
  • If you wish to treat the block as an object, it is possible to do so - a block can be wrapped in an instance of the Proc class (i.e. a Proc) e.g. via a block formal argument
    • A block within a Proc can be invoked by using the Proc's call method
  • A Proc can be unwrapped again into a block passed to a method via a block call argument
  • A block can be invoked with call arguments the same as a method
  • A block can accept formal arguments similar to a method, with some slight differences
  • A block has a return value
  • A block internally has "lambda" flag, which when set, alters the behaviour of the block slightly:
    • A block with lambda flag set accepts formal arguments the same way as a method
    • A block with lambda flag set has slightly different semantics for Ruby control transfer statements, such as return
  • A block can be manipulated in the following specific ways (excluding lambda expressions, see below):
    • A block is created in a method call, and passed to the called method for it to use, either by specifying a block or a block call argument in the method call
    • A method can invoke its passed block with the yield statement
    • A method can use other Ruby statements which do things with the block, such as super
    • A method can wrap its passed block in a Proc by accepting a block formal argument
    • A method can call specialized core methods that manipulate the passed block, such as block_given? which tests for the presence of a block

A Proc is an instance of the Proc class, whose purpose is to wrap a Block inside an object so it can be treated as an object. A Proc is therefore closer to what you might think of as an "anonymous method", especially if its block has the lambda flag set.

If you have a Proc, it is not possible in the core classes to alter its lambda flag or create a new Proc based off it with an altered lambda flag, except if you use the define_method method on a non-lambda proc, access that as a Method object and then convert the Method object to_proc - then it will be lambda.

A block with lambda flag set is typically created by:

  • the lambda method, which accepts a block and returns a Proc with that block and its lambda flag set (unless a block call argument was provided, in which case it preserves the lambda flag) e.g .lambda { |arg| puts arg }
  • equivalently, you can use the -> lambda expression syntax e.g. -> (arg) { puts arg }. Technically, this is a 4th type of unit of code possible in the Ruby syntax, and there are some slight differences due to this (alterations to the lambda method don't affect lambda expressions, and the syntax for lambda expression formal arguments is the same as for methods, not blocks), however in general its more useful to think of this as syntax shorthand for the lambda method

A "lambda" is a little tricky to define:

  • Normally, a "lambda" refers to a Proc created on a block with lambda flag set, however:
  • If you create such a Proc e.g. with lambda or -> and then unwrap it by passing it to a method via a block call argument, is the block thus passed (which has lambda flag set) a lambda? I will say yes, and use the term "lambda" interchangeably for these two concepts. If needed, I'll distinguish them by describing "lambda Procs" and "lambda blocks".

Syntax and formal arguments

Blocks (excluding lambda expressions) are specified after a method call between { and } or equivalently (almost) between do and end.

Optionally, the first thing inside these blocks can an expression between two | characters, containing (in order):

  • Optionally, the formal arguments specification and

  • Optionally, a ; followed by block variable declarations

     obj.method { block_code }                              # no arguments
     obj.method { || block_code }                           # no arguments
     obj.method { |argument_spec| block_code }              # arguments specified
     obj.method { |argument_spec; block_vars| block_code }  # arguments, block variables specified
     obj.method { |; block_vars| block_code }               # block variables specified
     obj.method do block_code end                           # all of the above options are available with "do ... end" syntax too
    

Lambda expressions have a slightly different syntax:

  • There is no || expression in the block

  • Arguments and block variables are specified between ( and ) directly before the block

  • The parentheses can be omitted, however block variables can't be declared if so

      -> { block_code }
      -> () { block_code }
      -> argument_spec { block_code }
      -> ( argument_spec ) { block_code }
      -> ( argument_spec; block_vars ) { block_code }
      -> ( block_vars ) { block_code }
      -> do block_code end # all of the above options are available with "do ... end"
    

Formal arguments

For -> lambda expressions, formal arguments are the same as those for methods.

For other blocks, formal arguments are specified slightly differently in the following aspects:

  • A block may specify a single mandatory argument, and no other arguments, followed by a trailing comma - which has a special meaning for non-lambda blocks
  • Due to a grammatical technicality, the expressions permitted as the default value of optional arguments have slightly different rules - see the Ruby grammar section for more details

When a block is invoked, its formal arguments are assigned according to "looser" rules than methods:

  • Mandatory and post arguments are not actually "mandatory", and in general there is no requirement for call arguments match up to formal arguments. Formal arguments which have no corresponding call argument are assigned nil, and extra unused arguments are ignored.
  • Call arguments available are assigned, left to right, to formal arguments in the following order:
  • Mandatory arguments
  • Optional arguments
  • If (and only if) a rest argument is specified, it is assigned the call arguments provided in excess of post arguments specified
  • Post arguments (note that if no rest argument is specified, these will therefore be assigned left to right and trailing arguments will be discarded)
  • There is a special splat rule if the block is invoked with a single non-block argument which is an array. In certain circumstances, the array will be automatically splatted (as if it was prefixed by * in the call arguments). This feature is designed as a syntax shorthand to allow blocks to specify that they expect potentially more than 1 argument (at least 1 of which is a mandatory or post) and that they also expect that they may called with a single argument which is an array, which should be splatted
  • The call argument is considered an array if it is an instance of the Array class (or subclass), or to_ary can be invoked on it and it does not return nil
  • The splat rule is normally enabled, and can't be disabled, with the following exceptions/special cases
  • If there are no mandatory or post arguments (i.e. only optional/rest/block arguments), the splat rule is disabled
  • If the only formal arguments are 1 mandatory and optionally a block argument, the splat rule is disabled
  • If the only formal argument is 1 mandatory argument, the splat rule can be explicitly enabled by following it with a trailing comma (i.e. |arg,|)
  • If you want to specify a single mandatory and a single block argument, but want to enable splatting, maybe throw an unused optional or rest argument in there
  • If you want to disable splatting altogether for blocks accepting multiple arguments, specify all mandatory/post arguments as optional arguments defaulting to nil (which is the same semantics as mandatory/post arguments anyway) - but if you wish to specify post arguments after a rest argument, you'll just have to roll the post arguments into a rest argument or something
  • Note that this logic is applied after the call arguments have already been processed into "simple" form. This means, for example, that if a block is invoked with an argument of *[[1, 2]], the outer array will be splatted leaving [1, 2] as the single "simple" argument, and then this inner array is eligible for re-splatting by this rule.

Block variable declarations

A block variable declaration is a comma-separated list of local variable names. This has the effect of explicitly declaring these local variables to be in the block's scope - if a local variable with the same name already exists in an outer scope for the block, it will be "shadowed" and inaccessible. See the variable scope section for more on this concept.

Clone this wiki locally