Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I reference a function in Ruby?

In python, it's fairly straightforward to reference a function:

>>> def foo(): ...     print "foo called" ...     return 1 ...  >>> x = foo >>> foo() foo called 1 >>> x() foo called 1 >>> x <function foo at 0x1004ba5f0> >>> foo <function foo at 0x1004ba5f0> 

However, it seems to be different in Ruby since a naked foo actually calls foo:

ruby-1.9.2-p0 > def foo ruby-1.9.2-p0 ?>  print "foo called" ruby-1.9.2-p0 ?>  1 ruby-1.9.2-p0 ?>  end  => nil  ruby-1.9.2-p0 > x = foo foo called => 1  ruby-1.9.2-p0 > foo foo called => 1  ruby-1.9.2-p0 > x  => 1  

How do I actually assign the function foo to x and then call it? Or is there a more idiomatic way to do this?

like image 298
Jason Baker Avatar asked Nov 27 '10 23:11

Jason Baker


People also ask

How does a function return a value in Ruby?

Ruby methods ALWAYS return the evaluated result of the last line of the expression unless an explicit return comes before it. If you wanted to explicitly return a value you can use the return keyword.

What does .first mean in Ruby?

The first() is an inbuilt method in Ruby returns an array of first X elements. If X is not mentioned, it returns the first element only. Syntax: range1.first(X) Parameters: The function accepts X which is the number of elements from the beginning. Return Value: It returns an array of first X elements.


1 Answers

Ruby doesn't have functions. It only has methods (which aren't first-class) and Procs which are first-class, but are not associated with any object.

So, this is a method:

def foo(bar) puts bar end  foo('Hello') # Hello 

Oh, and, yes, this is a real method, not a top-level function or procedure or something. Methods defined at the top-level end up as private(!) instance methods in the Object class:

Object.private_instance_methods(false) # => [:foo] 

This is a Proc:

foo = -> bar { puts bar }  foo.('Hello') # Hello 

Notice that Procs are called differently from methods:

foo('Hello')  # method foo.('Hello') # Proc 

The foo.(bar) syntax is just syntactic sugar for foo.call(bar) (which for Procs and Methods is also aliased to foo[bar]). Implementing a call method on your object and then calling it with .() is the closest thing you will get to Python's __call__ables.

Note that an important distinction between Ruby Procs and Python lambdas is that there are no restrictions: in Python, a lambda can only contain a single statement, but Ruby doesn't have the distinction between statements and expressions (everything is an expression), and so this limitation simply doesn't exist, therefore in a lot of cases where you need to pass a named function as an argument in Python because you cannot express the logic in a single statement, you would in Ruby simply pass a Proc or a block instead, so that the problem of the ugly syntax for referencing methods doesn't even arise.

You can wrap a method in a Method object (which essentially duck-types Proc) by calling the Object#method method on an object (which will give you a Method whose self is bound to that particular object):

foo_bound = method(:foo)  foo_bound.('Hello') # Hello 

You can also use one of the methods in the Module#instance_method family to get an UnboundMethod from a module (or class, obviously, since a class is-a module), which you can then UnboundMethod#bind to a particular object and call. (I think Python has the same concepts, albeit with a different implementation: an unbound method simply takes the self argument explicitly, just like the way it is declared.)

foo_unbound = Object.instance_method(:foo) # this is an UnboundMethod  foo_unbound.('Hello') # NoMethodError: undefined method `call' for #<UnboundMethod: Object#foo>  foo_rebound = foo_unbound.bind(self)       # this is a Method  foo_rebound.('Hello') # Hello 

Note that you can only bind an UnboundMethod to an object which is an instance of the module you took the method from. You cannot use UnboundMethods to "transplant" behavior between unrelated modules:

bar = module Foo; def bar; puts 'Bye' end; self end.instance_method(:bar) module Foo; def bar; puts 'Hello' end end  obj = Object.new bar.bind(obj) # TypeError: bind argument must be an instance of Foo  obj.extend(Foo) bar.bind(obj).() # Bye obj.bar # Hello 

Note, however, that both the Method and the UnboundMethod are wrappers around the method, not the method itself. Methods are not objects in Ruby. (Contrary to what I have written in other answers, BTW. I really need to go back and fix those.) You can wrap them in objects, but they aren't objects, and you can see that because you essentially get all the same problems you always get with wrappers: identity and state. If you call method multiple times for the same method, you will get a different Method object every time. If you try to store some state on that Method object (such as Python-style __doc__strings, for example), that state will be private to that particular instance, and if you try to retrieve your docstring again via method, you will find that it is gone.

There is also syntactic sugar in the form of the method reference operator .::

bound_method = obj.:foo 

Which is identical to

bound_method = obj.method(:foo) 
like image 198
Jörg W Mittag Avatar answered Sep 23 '22 17:09

Jörg W Mittag