Blocks: Javascript vs Ruby vs Python
2020-10-11 7 min
Blocks —a.k.a. closures or compound statements— are used to delimiter the scope of variables range. Think of it as a namespace mapping where variable names (keys) are bound to objects (values).
Each variable used within a code block is only visible in that specific context, preventing possible variable naming conflicts. This restriction does not apply for variables declared in parent contexts that are available within a block body.
The same variable name already existing in a parent scope can be redeclared in a block context storing any other different value. Within the block, the variable from the parent context won’t exist anymore.
It’s not a recommended practice to override variable names in the same file. It can lead to unexpected side effects as a negative impact on readability.
After this quick reminder, let’s see how different blocks are between Javascript, Ruby, and Python.
Javascript
Block delimiters in Javascript are usually expressed with curly braces or brackets ({}
).
// functions
function aFunction(){
// mutiple statements
}
// arrow functions
const aFunction = () => {
// mutiple statements
}
// conditionals
if (expression) {
// mutiple statements
} else if (expression) {
// mutiple statements
} else {
// mutiple statements
}
// case
switch (expression) {
case alpha: {
// mutiple statements
break;
}
case beta: {
// mutiple statements
break;
}
default: {
// mutiple statements
}
}
When statements are one-liner, there’s no need to use block delimiters. Anyway, it is recommended to use delimiters even for one-line sentences. It helps with readability.
// arrow function
const aFunction = () => // one stament
// conditionals
if (expression)
// one stament
else if (expression)
// one stament
else
// one stament
Notice that block scoping rules are the same no matter the command statement used. Blocks can be used anonymously, indeed.
let greet = 'hello';
{
let greet = 'bonjour';
console.log(greet); // bonjour
}
console.log(greet); //hello
Within a block scope, variables can be “redeclared” as in the above snippet. It is not a redeclaration but a new variable created in an isolated context.
Standalone blocks are not much useful. Blocks in javascript cannot be stored in any variable or used them as an argument. But, Javascript treats functions as first-class objects, so it makes more sense using blocks to keep a closure context in the form of a function.
const greet = () => {
return 'hello';
}
function say(fn) {
console.log(fn());
}
say(greet);
Because functions are first-class objects that may be assigned to variables, passed as arguments, or returned from other functions.
Ruby
Ruby blocks are different from Javascript. In Javascript, blocks are closed environments to keep variables isolated in the context of a statement or function. On the other hand, Ruby allows blocks to be stored and reused without the need for a context.
Functions cannot used as arguments. Well, yes, we can pass functions, indeed. But that’s not relevant to this article.
Ruby exposes three ways to declare a block: Blocks, Procs, and Lambdas.
Blocks are different from Proc’s and Lambdas as cannot be stored. Blocks are only used to declare a piece of code that’s going to be passed to a function.
prime_numbers = [1, 2, 3, 5, 7, 11, 13, 17, 19]
prime_numbers.each do |prime|
puts prime
end
The code surrounded by the do..end
statement is a block. When the code runs, the Array#each
method executes the received block for each item, passing the item as an argument to the block.
To run a block within a function, the yield
keyword must be used. So, the body of Array#each could look like below.
def each
i = 0
while i < size
yield at(i)
i += 1
end
end
yield
is a magic word that detects and executes the passed block.
Blocks can be implicit and explicit. Each method example uses an implicit block. The block argument is behind the scenes and executed by using the yield keyword.
Explicit blocks are declared as arguments in the function’s contract. The parameter holding the block must be prefixed with an ampresand symbol (&
).
The same Array#each
method can be rewritten as below.
def each(&block)
i = 0
while i < size
block.call at(i)
i += 1
end
end
When a block is explicitly declared, it is automatically converted into a Proc object.
A Proc is a block that can be stored in a variable and be used as an argument.
p = Proc.new do |n|
puts n
end
p.call 'hello'
When passing a Proc argument, there’s no need to prefix the parameter with an ampersand. It behaves like a normal argument.
It is important to mention that Blocks and Procs are executed in the very same context of a function. Using a return statement provokes the ending of the function execution, returning whatever the block returns.
And last but not least, Lambdas.
A Lambda is a Proc object but with function’s capabilities:
- An exception is raised when an argument is not provided.
- The return statement works as a normal function does.
l = lambda do |n|
puts n
end
Blocks can be declared with brackets notation when the body is just one line.
prime_numbers.each { |prime| puts prime }
Both notations are also available for Procs and Lambdas.
A big difference with Javascript is that ruby blocks do not keep isolated scopes. Unlike a function, a Ruby block does not create a namespace. It is executed in the same context where the action performs.
greet = 'hola'
p = Proc.new do
greet = 'bonjour'
puts greet
end
p.call # bonjour
puts greet # bonjour
greet
variable changes its value after executing the Proc. This applies even for Lambdas despite behaving as a function.
It is better to declare a function to keep isolated contexts.
greet = 'hola'
def fn
greet = 'bonjour'
puts greet
end
p.call # bonjour
puts greet # 'hola'
Python
Python blocks work much like Javascript. Instead of being wrapped by curly braces, code blocks are indented.
def a_function():
# mutiple statements
In Python —same as Javascript— functions are first-class objects. Functions can be assigned to variables, used as arguments, or returned from a function.
def fn():
def fn2():
print('kaixo')
return fn2 # returning a function
var_fn = fn() # assigning to a variable
var_fn() # kaixo
The scope of functions is also private. What’s is declared within a function, is not visible outside the function’s namespace. But upper contexts are available within.
greet = 'ciao'
def fn():
print(greet)
fn()
print(greet) # ciao
In a function body, the same variable name defined in the parent context can be declared again. Like in Javascript, the value won’t be modified.
greet = 'ciao'
def fn():
greet = 'olá'
def fn2():
print(greet)
return fn2
var = fn()
var() # olá
print(greet) # ciao
Python also allows creating another type of function called lambda. It is like Ruby blocks or Javascript anonymous functions. The difference with their counterparts, lambdas only can define one expression.
greet = lambda: 'hello'
print(greet()) # hello
Unlike Ruby blocks, Python lambdas respect the parent’s scope by creating its namespace.
greet = 'hola'
l = lambda: (
greet := 'bonjour',
print(greet)
)
l() # bonjour
print(greet) # hola
Don’t get wrong with this lambda. Even though it looks multiple statements are taking place in the lambda body, actually it is treated as one expression.
Resources
- docs.python.org/3/reference/executionmodel.html
- dev.to/redfred7/–python-slices-vs-ruby-blocks—5h3f
- stupidpythonideas.blogspot.com/2014/06/why-python-or-any-decent-language.html
- dbader.org/blog/python-first-class-functions
- medium.com/rubycademy/the-yield-keyword-603a850b8921
- hackr.io/blog/python-vs-javascript
- medium.com/swlh/ruby-closures-for-dummies-fbf846720c1f
- blog.appsignal.com/2018/09/04/ruby-magic-closures-in-ruby-blocks-procs-and-lambdas.html
- www.synbioz.com/blog/tech/block-proc-lambda-ruby
- stackabuse.com/lambda-functions-in-python
- stackoverflow.com/questions/6282042/assignment-inside-lambda-expression-in-python