Comparing Functions in JavaScript and Ruby

Ruby has a handful of constructs used to bundle repeatable code: methods, blocks, Procs, and lambdas. While overlapping behaviors apply to all of these, each type has unique optimizations and usage.

JavaScript, however, has just one function type with many roles.

How do they compare with each other? I set out to increase my understanding.

First, lets take a look at….

JavaScript Functions

Functions in JavaScript are blocks of repeatable code encapsulated in an object. They can define parameters which are set as local variable names within the function, accept values for those parameters as arguments upon invocation, and return values as a result of their routine.

Functions can be anonymous, single use, and declared as part of of another expression:

Or they can be named, and passed as arguments to other functions.

Functions are first class citizens and can be assigned to variables, saved in data structures, and passed into/returned from other functions. Functions are also closures: they create their own internal scope, but have access to all variables in scope at the time of their definition, even if that scope no longer exists.

When a function is defined as a property of an object, it is called a method. We can invoke this method in the context of its parent object using dot syntax.

Above, the keyword this refers to the current context, ie: the john_cena object. The speak function randomly samples from the array assigned to the property named catch_phrases in the current context.

Now — even functions defined outside of any object are implicitly stored as properties of the global object, so they too can reference the current context using this.

The context of a JS function is determined at runtime, not when the function is defined. Our method can be invoked in other contexts by using the call method, passing in a context object as an argument.

Much like call, we can create another function permanently bound to a specific execution context by invoking the bind method on our function, passing in a context object as an argument.

The concepts of context and binding (or lack thereof) are important to understand, as we will see, Ruby methods do retain binding to their parent object.

To clarify and summarize:

context: the object on which the function called. The keyword this refers to the current execution context. The context is determined by how the function is called, and defaults to the global object if no context is specified.

Functions in JS take on many roles — anonymous functions, methods, callbacks, factory functions, object constructors.. the list goes on and at first can be quite confusing. Lets switch gears and see how Ruby breaks up these roles into different types.

Ruby Methods

Ruby methods are procedures defined on a class, and inherited by all instances of that class. If defined outside of an explicit class definition, the method becomes a private method of the Object class, which all other classes inherit from.

Recreating our example from above:

Methods in Ruby are not objects, but can be wrapped up in an object by calling Object#method, passing in a symbol representing the method name. The resulting Method Object can be invoked with Method#call.

call in JS and Ruby are not synonymous. Method#call is how Method Objects are invoked, and arguments are listed after invocation between parentheses.

In JS, call is only used to invoke a method with a different context — the first argument is the context object, additional objects can come the context object.

Unlike our JS example, this Method Object remains bound to its parent object, and continues to execute in that context.

We don’t see the word context used so much in Ruby, though the concept of context is indeed there, just not as flexible as context in JS.

Methods in Ruby are always called on a receiving object. All objects are instances of a class, and methods are defined on an object’s class.

To think of it another way — methods are messages sent to an object, which responds by looking up the method name in its lookup chain, and executing the first match it finds. Self within a method is the object that receives the message, looks up the method, and executes the code. This is all in keeping with Ruby being a pure, class-based object oriented language.

We can somewhat mimic JavaScript’s explicit function execution context with Unbound Methods.

Unbound methods can only be re-bound to objects of the original method’s class, so at this point in my understanding, I am not sure what utility this provides, as the new ‘context object’ would already have access to the unbound method via class inheritance.

Blocks

A block in Ruby is a chunk of code wrapped in curly brackets or do..end keywords. It is syntax, not an object. Blocks are only found immediately following a method invocation, and only one single block can be passed into a method. This block can be accessed with two keywords within the method:

  • block_given? checks for presence of the block
  • yield gives control to the block, optionally passing arguments to it.

Two simple examples:

Above we see a couple of key features of blocks

  • Methods can be designed to require blocks, accept blocks if they are given, or completely ignore them depending on their usage of yield and block_given?
  • Blocks are closures, and carry with them the variables in scope at the time of their definition.
  • Methods can pass arguments to the block via the yield keyword. The corresponding parameters are defined between pipe characters and separated by commas at the start of the block.
  • Missing arguments are set to nil, extra arguments are ignored.

The most common usage of blocks is iteration over collections. Rather than writing methods for every possible use case, we can use built in Enumerable methods, or define our own custom iterations to generically pass values into any block we choose.

This is a really approachable and readable way to introduce functions as arguments, without the overhead of defining a separate function, or even introducing functions as objects. Our passed in block isn’t an argument, and it isn’t an object. It is just a customizable part of the method invocation.

Procs and The Ampersand

A Proc is a Ruby object type that wraps up a block. It can define parameters, accept arguments, return values, be saved to a variable, and passed into/returned from other function types. Procs are invoked using Proc#call.

Procs are closures, and can access the local variables in scope at the time of their definition, even after that scope has been returned from.

Earlier we defined a method accepting an implicit block and demonstrated some block behaviors. This same method can accept a Proc object in place of the block:

Notice how our proc is immediately preceded by an ampersand, and passed in between parentheses as if it were a method argument. Like our block before, this isn’t a normal argument. Ruby methods strictly obey their arity, and there were no parameters defined for this method. So why does this work?

The ampersand tells us: “use this as our special ‘block argument’ accessible by yield and block_given?”.

Also notice, we didn’t have to change our original method, though we could if we wanted to reference the proc by name within the method.

In our method declaration we also use the ampersand to say “this isn’t a normal parameter, this is the name used to reference the special block argument so yield and block_given? can see it. If this is a block — use it. If it is an object prefixed with an ampersand, call to_proc on it and use it”

So..we can also pass a block to a method with an explicit block/proc parameter.

Within the method call above, our passed in block is a real life Proc, object ID and all.

This is really quite flexible, in fact, any object with a to_proc method defined can be passed into a method this way:

If we want to pass multiple Procs to a method, we can abandon the ‘ampersand/yield’ paradigm and pass in Procs as normal arguments.

More often than not, it is simpler and more elegant to use a block than to take the extra step and define a Proc, especially if it only gets used once. But perhaps we need to generate a block dynamically from a factory method based on user input, or maybe we have a specific block used repeatedly throughout our application. This is where Procs come in handy.

Lambdas

Lambdas are also Proc objects, so they share all of the above behavior, and can generally be used interchangeably with Procs, with two main exceptions:

  • Arity: Procs do not strictly check the number of passed in arguments, and will even do you some favors by packing/unpacking arguments to/from arrays to match the defined parameters. Lambdas strictly obey their arity, and do you no favors.
  • return statements in lambdas return from the lambda themselves, much like how a JS function or Ruby method returns from themselves.
  • return statements in Procs return from the scope where the proc was defined — much like blocks. This scope may not exist at runtime, or might be the top level main object, which cannot be returned from. Both of these circumstances result in a LocalJump Error.

Most often we will rely on our functions implicitly returning the value of the final expression, other times we’ll want an conditional early return statement. With Procs and lambdas both available, we have the flexibility to choose whether this explicit return will terminate the entire method call like a block, or just the single iteration, like a method.

The Finish

I’m about one year into learning Ruby, and had been comfortably swimming in object/method/block territory until about two weeks ago.

Ruby was OK with this— it didn’t mind if didn’t think about first class functions. I still was able to solve problems at work, school, and in personal projects. Matz wants me to be comfortable and happy, and I was both!

Ruby’s functions are broken up into four different types, and specialized for their specific use case. This is more complicated in design, yet simpler in application; easier to read, use, and understand. In contrast, JavaScript’s single function type is more powerful and flexible, playing many roles within the language.

This article was the result of much research from many (often contradictory!) sources. If anything is unclear or flat out wrong, let me know in the comments.

Thanks for reading!

Software Engineer, Co-Author of XorroP2P: a BitTorrent-Like P2P File Sharing application built from scratch in Ruby

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store