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:

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