1

Functions in Ruby are not first class objects, but I would like to be able to pass a reference to a function into another function and have it be executed. How can this be done?

Example:

def run_a_function_twice(my_function)
  # Call the function once.
  # Call the function again.
end

def say_hello
  puts "HI!"
end

run_a_function_twice(say_hello)

I'm reading the docs, but not sure if I should be trying out lambdas, procs, or call (I'm only familiar with the concept of call from other languages)

4 Answers 4

11

You can do this in two different ways:

  1. Pass the name of the method (usually as a symbol, but a string will work too):

    def run_a_method_twice(method_name)
      send(method_name)
      send(method_name)
    end
    
    run_a_method_twice(:say_hello)
    

    This depends on say_hello being available in the same scope as run_a_method_twice, e.g. if they are both instance methods on the same class. If say_hello was defined on another object, you would do e.g. some_obj.send(:say_hello).

    You can supply arguments to the method by giving them to send after the method name, e.g. jordan.send(:say_hello, "Donny").

  2. Use a block:

    def yield_to_a_block_twice
      yield
      yield
    end
    
    yield_to_a_block_twice { say_hello }
    

    This syntax works as well:

    def call_a_block_twice(&block)
      block.call
      block.call
    end
    

    You should use yield when possible (it's faster), but sometimes it's necessary to be able to refer to the block by name (for example if you need to pass it to another method, or call it from within another block), in which case making it a named argument (i.e. def meth(arg1, arg2, &block_name)) is necessary.

    The distinctions between block, Proc, and lambda are challenging to Ruby newcomers, and much has written about them—just google "Ruby block proc lambda." Here's a pretty good article to get you started: http://awaxman11.github.io/blog/2013/08/05/what-is-the-difference-between-a-block/

Sign up to request clarification or add additional context in comments.

Comments

8

This is done by passing methods via blocks. There are several syntaxes for this.

The first syntax uses yield and looks something like this.

def method1
  puts "This is from method 1"
  yield
end

def method2
  puts "This is from method 2"
end

method1(){method2}

The above will output

This is from method 1

This is from method 2


The second option uses the following syntax

def method1(&block)
  puts "This is from method 1"
  block.call
end

def method2
  puts "This is from method 2"
end

method1(){method2}

The output results are the same. Typically the yield syntax is preferred as it is more terse, but also because it is on average about 5X as fast as the block.call notation.


The third option is to use the send syntax which looks like follows.

def method1(method_name_string)
  puts "This is from method 1"
  send(method_name_string, 1, 2)
end

def method2(a,b)
  puts "This is from method 2" + a.to_s + ' ' + b.to_s
end

method1("method2")

You can also achieve something similar using lambda or Proc.new

def method1(lmb)
  puts "This is from method 1"
  block.call "A string"
end

foo = lambda do |x|
  puts x
end

method1(foo)

In this case you would see

This is from method 1

A string

Comments

5

Generally in ruby there is the concept of blocks which are anonymous methods (closures) if you will.

You can pass a block to any method. The method can then either use yield (in conjunction with block_given?) to call the method/block or reference it to a variable with the & operator. The latter can be used to store the reference or pass it to another method.

def call_it_twice
  2.times {|i| yield(i) }
end

call_it_twice { puts "hello" }
# hello
# hello

call_it_twice {|i| puts "hello #{i}" }
# hello 0
# hello 1

def call_it_thrice &block
  call_it_twice(&block)
  block.call(2)
end

call_it_thrice {|i| puts "hello #{i}" }
# hello 0
# hello 1
# hello 2

You can also pass a literal method but it's uncommon.

class Foo
  def hello
    puts "world"
  end
end

Foo.instance_methods(:hello)
# #<UnboundMethod: Foo#hello>

Foo.instance_method(:hello).call
# NoMethodError: undefined method `call' for #<UnboundMethod: Foo#hello>

Foo.instance_method(:hello).bind(Foo.new).call
# world


Foo.new.method(:hello)
# #<Method: Foo#hello>

Foo.new.method(:hello).call
# world

A common thing is to write array.map{|x| x.downcase } as array.map(&:downcase) (this is a shortcut for to_proc so it's array.map(&:downcase.to_proc) behind the scenes).

Relatively unknown though is this: array.each{|x| puts x } is the same as array.each(&method(:puts))

Comments

1

Functions in Ruby are not first class objects...

That just isn't true. First, Ruby doesn't really have functions. When you define a "function", Ruby actually adds an instance method to Object. This has the same effect as defining a function since everything is an object, so the method can always be called.

Second, methods are first class objects, they're just a little harder to access since Ruby always thinks you want to call the method instead of accessing the method object directly. The method method gets you the object.

def run_a_function_twice(my_function)
  2.times { my_function.() }
end

def say_hello
  puts "HI!"
end

run_a_function_twice(method(:say_hello))

2 Comments

Thanks for the answer Max! How come you use my_function.() instead of my_function.call()?
No particular reason. It does the same thing, just shorter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.