This post is the first in the series of “From JavaScript to Ruby”, which is aimed at helping JavaScript developers transition their thinking from JavaScript to Ruby. This post will answer this fundamental question: how do I do “callbacks” in Ruby? The answer is: the “idiomatic” way (we’ll talk about what “idiomatic” means) to use so-called callbacks in Ruby is to use blocks. We’ll discuss blocks, and their similarities to JavaScript callbacks.
Defining “callback” through practical examples
A callback is a function that happens after we call another function, but the catch is, they’re coupled. However, being coupled in this way doesn’t mean we can’t reuse the callback function.
We often use anonymous functions in JavaScript as callbacks. Here’s an example using Array.prototype.forEach
:
We can actually sort of “scoop out” the function that we passed into Array.prototype.forEach
in order to make it reusable:
How do we do this in Ruby? And what does “idiomatic” mean?
You’ll hear a lot of Rubyists use the word “idiomatic”. Read:
The idiomatic way to pass arguments to a method in Ruby is such and such…
The idiomatic way to use a “callback” in Ruby is to such and such…
In normal-people speak, you’d say:
The common way to go about doing this is such and such…
Make sense? In other words, there are certain patterns and best practices (solutions) for common problems in Ruby: we call these solutions “idioms”. So, the idiomatic way to use callbacks in Ruby is not to use callbacks at all. Instead, we use blocks.
Here’s how we can do the same exact thing in Ruby, instead of JavaScript:
If you run the above, you’ll see Hello <name>
printed for each name, and at the end it’ll actually return the entire array for you to use.
Let’s compare the two. Essentially, this JavaScript…:
…is the same as this Ruby:
We can also write the above Ruby in another, shorter way:
So here’s a few things we’ve learned from the above.
1st thing we learned (string interpolation)
Both ways are correct, but the string interpolation notation is more idiomatic.
In JavaScript, we call it “string concatenation”:
2nd thing we learned (block syntax)
We also learned two styles, or syntaxes, for writing blocks:
Pro Tip
Before we continue, I want to point out a couple style points. Rubyists often use "soft tabs", or 2 spaces instead of just a 4-character tab, only because it different environments it remains readable (see this StackOVerflow question). Also, look at the spacing between the curly braces and pipes. I recommend something to you: Rubocop, which is sort of the de-facto command line tool that tells you if your code looks bad according to the community style guide on GitHub. It's good stuff.
3rd thing we learned (Enumerable#each == Array.prototype.forEach
)
Continuing, we learned that Ruby’s Enumerable#each
is equivalent to JavaScript’s Array.prototype.forEach
(more or less). I have found only one online blog post so far that has made Ruby’s #each
make sense to me. It is by Erik Trautman.
“Scooping out” the block like a reusable callback
There are two common ways to make a reusable “callback” in Ruby. They are the lambda
and the Proc
. There are very subtle differences between the two ways.
Lambda
By the way, that’s the new syntax for the single-line lambda as of Ruby 1.9 (I have Ruby 2.1 right now, if you’re curious). If you want to do a multi-line lambda, you do:
Now that’s some weird-ass looking syntax, huh? Let’s make it look even weirder:
Proc (short for “procedure”)
Did you notice, by the way, that you can do multi-line variable assignment? Pretty cool; you can’t do that in JavaScript.
Differences
- Lambda:
- You don’t have to pass in all the arguments if you don’t want to. If your “callback” needs, say, 3 arguments, and you only pass in 2 when you use it, there will be no error. This is just like a JavaScript callback… but in JavaScript, the undefined arguments will be
undefined
, and in Ruby, the undefined arguments will benil
. - If you call
return <whatever>
, the lambda stops, but any method that the lambda is in does not stop. You can use the lambda’s returned value in the rest of the containing method.
- You don’t have to pass in all the arguments if you don’t want to. If your “callback” needs, say, 3 arguments, and you only pass in 2 when you use it, there will be no error. This is just like a JavaScript callback… but in JavaScript, the undefined arguments will be
- Proc:
- You must pass in all the arguments, or you’ll get an error.
- If you call
return <whatever>
, the Proc and any method that it is in will stop. The Proc will return its value to the containing method, and the containing method will also return that same value.
lambda
in JavaScript:
Proc
in JavaScript:
In Ruby, though, as a side note, we rarely use an explicit return
statement. You can just type a value.
This JavaScript…:
…is equivalent to this Ruby:
The ampersand (&
)
This confusing thing basically just makes sure the thing you’re passing into the method is a Proc, sort of. You can read about that in detail on a.blog.about.code.
The above error is basically saying, “why did you pass me an argument? I expected 0 arguments for #each
”. &sayHello
is not an argument! It is a Proc
, and Ruby does not count Proc
s as arguments!
It’s weird, huh? That’s because you can pass in a block at the end of pretty much any method in Ruby, and it won’t see it as an argument, in the typical sense.
Check it out:
We passed in a block at the end, but it didn’t run. But we didn’t get any error either. If we want the block to actually run, we can just say yield
:
#block_given?
is a native method that returns a boolean (true
or false
) that checks if a block was passed in to the method. We have to use it in the above example, because if we just say yield
instead of yield if block_given?
, then we’ll get an error that says no block given (yield)
.
Also, the question mark ?
is Ruby’s way of meaningfully letting the user know that the method is supposed to return a boolean. There’s nothing really special about it.
Now, going back to the original error (error: wrong number of arguments (1 for 0)
):
I’ll try my best to sort of construe a similar error in JavaScript:
If the above code were Ruby, you’d get an error something like wrong number of arguments (2 for 1)
. Once again, I want to emphasize this is because Ruby doesn’t see the Proc
as an argument.
Conclusion
- Use blocks as if they were callbacks.
- Use a
lambda
or aProc
. Remember the two subtle differences between them (number of arguments, and return values). - Put a
&
before the block that you pass into the method, if you pass it in to a native Ruby method that will run a block, likeEnumberable#each
.
Further reading
- Reactive.IO: Understanding Ruby Blocks, Procs, and Lambdas
- makandracards: Short Lambda Syntax in Ruby 1.9