In Ruby, is the if / elsif / else sub-block the same as the "block" that is passed as a parameter?
I read about if / elsif / else in Ruby and I ran into some differences in terminology when describing how control expressions work.
In the Ruby Programming Wikibooks (emphasis mine):
The conditional branch takes the result of the test expression and executes the block of code depending on whether the test expression is true or false.
and
If an if statement, for example, not only determines whether a subordinate block of code will be executed , but also results in the value itself.
Ruby-doc.org, however, does not mention blocks at all in the definitions:
The simplest if expression consists of two parts: the "test" expression and the "then" expression. ... If the expression "test" evaluates to true, then the expression "then" is evaluated.
Usually when I read about "blocks" in Ruby, it is almost always in the context of procs and lambdas. For example, rubylearning.com defines a block:
Ruby block is a way of grouping operators and can only be displayed in a source adjacent to a method call; block written starting at the same line as the last parameter of the method call (or the closing parenthesis of the parameter list).
Questions:
- When talking about blocks of code in Ruby, are we talking about a group of code that is passed to a method, or are we just talking about a group of code in general?
- Is there a way to easily distinguish between the two (and is there a technical difference between the two)?
The context for these questions: I'm interested in referring to code inside conditionals, as blocks will be confused with new Ruby programmers when they are later exposed to blocks, procs and lambdas.
source to share
TL; DR if...end
is an expression, not a block
The correct use of the term block
in Ruby is code passed to the method between do...end
or curly braces {...}
. A block can, and often is, implicitly converted to Proc
within a method using the syntax &block
in the method signature. This new one Proc
is an object with its own methods that can be passed to other methods, stored in variables and data structures, called multiple times, etc.
def block_to_proc(&block)
prc = block
puts prc
prc.class
end
block_to_proc { 'inside the block' }
# "#<Proc:0x007fa626845a98@(irb):21>"
# => Proc
In the above code, a is Proc
implicitly created with a block as its body and assigned to a variable block
. Likewise, Proc
(or lambda
, type Proc
) can be "expanded" into blocks and passed to methods that expect them, using the syntax &block
at the end of the argument list.
def proc_to_block
result = yield # only the return value of the block can be saved, not the block itself
puts result
result.class
end
block = Proc.new { 'inside the Proc' }
proc_to_block(&block)
# "inside the Proc"
# => String
Although there is a two-way street between block
and Proc
s, they don't match. Note that to define a, Proc
we need to pass block
in Proc.new
. Strictly speaking, block
it is just a piece of code passed to a method, the execution of which is deferred until an explicit call. A is Proc
defined with block
, its execution is also deferred until called, but it is a bonafide object like any other. A block
can't survive on its own, Proc
maybe.
On the other hand, block
or block of code
sometimes accidentally used to refer to any discrete piece of code enclosed in the Ruby keywords, ending in end
: if...else...end
, begin...rescue...end
, def...end
, class...end
, module...end
, until...end
. But they don't really block, in fact, and only actually resemble them on the surface. They often also have pending execution until some condition is met. But they can stand entirely on their own and always have return values. The use of the expression "expression" in Ruby-doc.org is more precise.
From wikipedia
An expression in a programming language is a combination of one or more explicit values, constants, variables, operators, and functions that the programming language interprets (according to its particular rules of precedence and association) and computes to produce ("to return", in a stateful environment ) other meaning.
This is why you can do things like this
return_value = if 'expression'
true
end
return_value # => true
Try to do it with a block
return_value = do
true
end
# SyntaxError: (irb):24: syntax error, unexpected keyword_do_block
# return_value = do
# ^
A block is not an expression in and of itself. This requires either yield
or switching to Proc
. What happens when we pass a block to a method that doesn't want it?
puts("indifferent") { "to blocks" }
# "indifferent"
# => nil
The block is completely lost, it disappears with no return value, no execution, as if it never existed. To complete the expression and get the return value is required yield
.
class Object
def puts(*args)
super
yield if block_given?
end
end
puts("mindful") { "of blocks" }
# "mindful"
# => "of blocks"
source to share