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:

[1, 2, 3, 4].map(function(number) {
return number * number
})
// [ 1, 4, 9, 16 ]

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

function square(number) { return number * number }
[1, 2, 3, 4].map(square)
// [ 1, 4, 9, 16 ]

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.

function make_divider(divisor) {
var words = "your answer is: ";
return function(dividend) {
return words + (dividend / divisor);
}
}
var quarter = make_divider(4)
var third = make_divider(3)
quarter(100)
// 'your answer is: 25'
third(60)
// 'your answer is: 20'

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.

var john_cena = {
catch_phrases: ["The champ is here!", "Hustle, Loyalty, Respect", "U Cant See Me"],
speak: function() {
var length = this.catch_phrases.length;
return this.catch_phrases[Math.floor(Math.random() * length)];
}
}
john_cena.speak();
// 'Hustle, Loyalty, Respect'
john_cena.speak();
// "The champ is here!"
speak();
// ReferenceError: speak is not defined

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.

this
// { DTRACE_NET_SERVER_CONNECTION: [Function],
// DTRACE_NET_STREAM_END: [Function],
// many many lines of global object properties
function what_is_this() {
console.log(this)
}
what_is_this()
// { DTRACE_NET_SERVER_CONNECTION: [Function],
// DTRACE_NET_STREAM_END: [Function],
// many many lines of global object properties

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.

var hulk_hogan = {
catch_phrases: ["Whatcha gonna do brother??", "Let me tell ya something Mean Gene.."]
}
var ric_flair = {
catch_phrases: ["Woooo", "To be the man, you've got to beat the man"]
}
var catch_phrases = ["Who let the dogs out?", "Keepin' it real"]
john_cena.speak.call(ric_flair)
// "Woooo"
var talk = john_cena.speak
talk.call(hulk_hogan)
// "Whatcha gonna do brother??"
talk()  // no context specified - defaults to global context
// "Who let the dogs out?"
what_is_this.call(hulk_hogan)
// { catch_phrases:
// [ 'Whatcha gonna do brother??',
// 'Let me tell ya something Mean Gene..' ] }

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.

var hulk_up = talk.bind(hulk_hogan)
hulk_up()
// "Let me tell ya something Mean Gene.."
hulk_up()
// "Whatcha gonna do brother??"
hulk_up.call(ric_flair)
// "Whatcha gonna do brother??" ---- bound function cannot have its context changed

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:

class Wrestler
attr_accessor :catch_phrases
def initialize(catch_phrases)
@catch_phrases = catch_phrases
end

def speak
@catch_phrases.sample
end
end
hulk_hogan = Wrestler.new(["Whatcha gonna do brother??", "Let me tell ya something Mean Gene.."])
ric_flair = Wrestler.new(["Woooo", "To be the man, you've got to beat the man"])
ric_flair.speak
// "Woooo"
hulk_hogan.speak
// "Whatcha gonna do brother??"

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.

hulk_up = hulk_hogan.method(:speak)
hulkup.call
// "Whatcha gonna do brother??"

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 = hulk_up.unbind
// => #<UnboundMethod: Wrestler#speak>
unbound.bind(ric_flair).call
// => "To be the man, you've got to beat the man"

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:

class Example
def takes_implicit_block
x = 1
puts "self in the method is #{self}"
puts "x in the method is #{x}"
puts "block has #{block_given? ? 'been' : 'not been'} given"
yield "hello from the method" if block_given?
end
end
x = 1000
my_example = Example.new
my_example.takes_implicit_block
// self in the method is #<Example:0x007f8642a3f230>
// x in the method is 1
// block has not been given
// => nil
my_example.takes_implicit_block do |saying, ignored_argument|
puts "self in the block is #{self}"
puts "method has yielded: #{saying}"
puts "x in the block is #{x}"
end
// self in the method is #<Example:0x007f8642a3f230>
// x in the method is 1
// block has been given
// self in the block is main
// method has yielded: hello from the method
// x in the block is 1000
// => nil

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.

wrestlers = [john_cena, ric_flair, hulk_hogan]
wrestlers.map { |wrestler| wrestler.speak }
=> ["U Can't See Me", "To be the man, you've got to beat the man", "Let me tell ya something Mean Gene.."]
wrestlers.select { |wrestler| wrestler.catch_phrases.size == 3 }
=> [#<Wrestler:0x007fb3f410dd58 @catch_phrases=["The champ is here!", "U Can't See Me", "Hustle, Loyalty, Respect"]>]

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.

square = Proc.new { |num| num ** 2 }
square.call(10)
// 100
square.call(-3)
// 9

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.

make_divider = Proc.new do |divisor|
words = "your answer is: "
Proc.new do |dividend|
words + (dividend / divisor).to_s
end
end
quarter = make_divider.call(4)
third = make_divider.call(3)
quarter.call(100)
// "your answer is: 25"
third.call(90)
// "your answer is: 30

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:

x = 1001
my_proc = Proc.new do |saying|
puts "self in the block is #{self}"
puts "method has yielded: #{saying}"
puts "x in the proc is #{x}"
end
my_example.takes_implicit_block(&my_proc)
// self in the method is #<Example:0x007f8642979e90>
// x in the method is 1
// block has been given
// self in the block is main
// method has yielded: hello from the method
// x in the proc is 1001
// => nil

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.

class Example
def explicit_block_with_ampersand(&blok)
puts "you passed in a #{blok.class} with id #{blok.object_id}"
puts "block has #{block_given? ? 'been' : 'not been'} given"
blok.call('this is the proc being called')
yield('this is the block being yielded to')
end
end
P = Proc.new do |saying|
puts saying
end
my_example.explicit_block_with_ampersand(&P)
// you passed in a Proc with id 70107309288260
// block has been given
// this is the proc being called
// this is the block being yielded to
// => nil

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.

my_example.explicit_block_with_ampersand { |saying| puts "#{saying}, Whoah" }
// you passed in a Proc with id 70107309187360
// block has been given
// this is the proc being called, Whoah
// this is the block being yielded to, Whoah

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:

class Fixnum
def to_proc
Proc.new {|i| self + i }
end
end
[1,2,3,4,5].map(&20)
// [21, 22, 23, 24, 25]

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

class Example
def takes_normal_arguments(*args)
puts "you passed in #{args.size} arguments"
puts "block has #{block_given? ? 'been' : 'not been'} given"
args.map(&:call)
end
end
a = Proc.new { 'A' }
b = Proc.new { 'B' }
c = Proc.new { 'C' }
my_example.takes_normal_arguments(a, b, c)
// you passed in 3 arguments
// block has not been given
// => ["A", "B", "C"]

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!