loading...

Passing Functions as Arguments in Ruby

halented profile image Hal Dunn ・2 min read

It can be done! As per my last blog post, with no further ado, here is how you do it:

def first_option
    puts "space jam"
end

def second_option
    puts "dogs rule"
end

def receives_function(func)
   method(func).call
end

receives_function(:first_option)

It is imperative to notice that you must pass the function through as a symbol when you are ready to send it along.

There are a few things about this setup that you may find interesting. When I first started looking into this, I assumed that you could just pass them through normally, using receives_function(first_option). I figured, Ruby is nice right?

Wrong.

Just kidding, I mean Ruby is nice, but it's not magic. Because Ruby is a synchronous language and function calls only require the name of the function, Ruby will read receives_function(first_option) and immediately invoke the first_option method, before it enters receives_function method at all.

So let's dive into the other stuff. The symbol terminology is Ruby's built-in way to allow you to reference a function without calling it. By placing the symbol in the argument for receives_function, we are able to pass all the info along and actually get into the receives_function code block before executing anything.

The next step is to figure out how to call a function which has been sent as a symbol. It gets a little hairy here because once we are inside of the receives_function code block, all we have to work with is a variable called func. Not a symbol at all. This is where Ruby's method method comes into play.

As a side note, googling "Ruby method method" is marginally annoying, so here is the link to the Ruby Docs to save you the time if you're interested.

The method method takes an argument of a symbol and returns information about that symbol. When you pass it a symbol of another method, it will return something like this:

[5] pry(main)> method(func)
=> #<Method: main.first_option>

With this it's saying hey, you passed me a method, and its name is first_option. You can chain a few commands on the end of this return value to determine other information about the symbol. For example, you could add .source_location and it would give you the file name and the line on which the original first_option function was defined. That's very useful for debugging, but in our case we don't care about that -- we just wanna invoke it. So, as you saw in the example, we chain a .call on there and "call" it a day.

Sigh.

So that's that! Have fun out there.

Posted on by:

halented profile

Hal Dunn

@halented

a softie and a software engineer

Discussion

pic
Editor guide
 

Note that the slightly more idiomatic approach is to pass in the method object as an argument:

receives_function(method(:first_option))

This way receives_function can be blissfully ignorant as to what func is, as long as it responds to call (so we could also pass in a lambda).

You can even use default arguments:

def receives_function(func = method(:first_option))
  func.call
end

This way there's a default argument and we can override it as needed. This can be quite useful at times, i.e. when you have a default behavior that you need most of the time (the method in the same scope), but occasionally want to override it (i.e. in a test).

 

Great post! Thank you!

 

Very well described. I did not understand this very well.