ab217
ab217

Reputation: 17160

Ruby: Does defining a method inside another method have any real uses?

I was reading an article on meta-programming and it showed that you can define a method within another method. This is something that I had known for a while, but it made me ask myself the question: does this have any practical application? Is there any real life uses of defining a method within a method?

Ex:

def outer_method
  def inner_method
     # ...
  end
  # ...
 end

Upvotes: 13

Views: 7836

Answers (5)

Peeja
Peeja

Reputation: 14264

Not using def. There is no practical application for that, and the compiler should probably raise an error.

There are reasons to define a method dynamically during the course of executing another method. Consider attr_reader, which is implemented in C, but could be equivalently implemented in Ruby as:

class Module
  def attr_reader(name)
    define_method(name) do
      instance_variable_get("@#{name}")
    end
  end
end

Here, we use #define_method to define the method. #define_method is an actual method; def is not. That gives us two important properties. First, it takes an argument, which allows us to pass it the name variable to name the method. Second, it takes a block, which closes over our variable name allowing us to use it from inside the method definition.

So what happens if we use def instead?

class Module
  def attr_reader(name)
    def name
      instance_variable_get("@#{name}")
    end
  end
end

This doesn't work at all. First, the def keyword is followed by a literal name, not an expression. That means that we're defining a method named, literally, #name, which isn't what we wanted at all. Second, the body of the method refers to a local variable called name, but Ruby won't recognize it as the same variable as the argument to #attr_reader. The def construct doesn't use a block, so it doesn't close over the variable name anymore.

The def construct doesn't allow you to "pass in" any information to parameterize the definition of the method you're defining. That makes it useless in a dynamic context. There's no reason to define a method using def from within a method. You could always move the same inner def construct out of the outer def and end up with the same method.


Additionally, defining methods dynamically has a cost. Ruby caches the memory locations of methods, which improves performance. When you add or remove a method from a class, Ruby has to throw out that cache. (Before Ruby 2.1, that cache was global. As of 2.1, the cache is per-class.)

If you define a method inside another method, each time the outer method is called, it invalidates the cache. That's fine for top-level macros like attr_reader and Rails' belongs_to, because those are all called at when the program is starting up and then (hopefully) never again. Defining methods during the ongoing execution of your program will slow you down quite a bit.

Upvotes: 4

EdvardM
EdvardM

Reputation: 3072

I think there is another benefit to using inner methods which is about clarity. Think of it: a class with list of methods is a flat, non-structured list of methods. If you care about separation of concerns and keeping stuff in the same level of abstraction AND the piece of code is used only in one place, inner methods come to help while strongly hinting that they are used only in enclosing method.

Assume you have this method in a class:

class Scoring
  # other code
  def score(dice)
    same, rest = split_dice(dice)

    set_score = if same.empty?
      0
    else 
      die = same.keys.first
      case die
      when 1
        1000
      else
        100 * die
      end
    end
    set_score + rest.map { |die, count| count * single_die_score(die) }.sum
  end

  # other code
end

Now, that's sort of simple data structure transformation and more higher-level code, adding score of dice forming a set and those which do not belong to the set. But not very clear what's going on. Let's make it more descriptive. A simple refactoring follows:

class Scoring
  # other methods...
  def score(dice)
    same, rest = split_dice(dice)

    set_score = same.empty? ? 0 : get_set_score(same)
    set_score + get_rest_score(rest)
  end

  def get_set_score(dice)
    die = dice.keys.first
    case die
    when 1
      1000
    else
      100 * die
    end
  end

  def get_rest_score(dice)
    dice.map { |die, count| count * single_die_score(die) }.sum
  end

  # other code...
end

Idea of get_set_score() and get_rest_score() is to document by using a descriptive (though not very good in this concocted example) what those pieces do. But if you have lots of methods like this, code in score() is not that easy to follow, and if you refactor either of the methods you might need to want to check what other methods use them (even if they are private - other methods of the same class could use them).

Instead, I'm starting to prefer this:

class Scoring
  # other code
  def score(dice)
    def get_set_score(dice)
      die = dice.keys.first
      case die
      when 1
        1000
      else
        100 * die
      end
    end

    def get_rest_score(dice)
      dice.map { |die, count| count * single_die_score(die) }.sum
    end

    same, rest = split_dice(dice)

    set_score = same.empty? ? 0 : get_set_score(same)
    set_score + get_rest_score(rest)
  end

  # other code
end

Here, it should be more obvious that get_rest_score() and get_set_score() are wrapped into methods to keep logic of score() itself in the same abstraction level, no meddling with hashes etc.

Note that technically you can call Scoring#get_set_score and Scoring#get_rest_score, but in this case it would be bad style IMO, because semantically they are just private methods for the single method score()

So, having this structure you are always able to read the whole implementation of score() without looking any other method defined outside Scoring#score. Even though I don't see such Ruby code often, I think I'm going to convert more into this structured style with inner methods.

NOTE: Another option that doesn't look as clean but avoids the issue of name clashes is to simply use lambdas, which has been around in Ruby from the get go. Using the example, it would turn into

get_rest_score  = -> (dice) do
  dice.map { |die, count| count * single_die_score(die) }.sum
end
...
set_score + get_rest_score.call(rest)

It isn's as pretty -- someone looking at the code might wonder why all these lambdas, whereas using inner methods is pretty self-documenting. I'd still lean more towards just lambdas, as they don't have the issue of leaking potentially conflicting names to current scope.

Upvotes: 5

TomDogg
TomDogg

Reputation: 3937

I was thinking of a recursive situation, but I don't think it would make enough sense.

Upvotes: 0

glenn mcdonald
glenn mcdonald

Reputation: 15488

My favorite metaprogramming example like this is dynamically building a method that you're then going to use in a loop. For example, I have a query-engine I wrote in Ruby, and one of its operations is filtering. There are a bunch of different forms of filters (substring, equals, <=, >=, intersections, etc.). The naive approach is like this:

def process_filter(working_set,filter_type,filter_value)
  working_set.select do |item|
    case filter_spec
      when "substring"
        item.include?(filter_value)
      when "equals"
        item == filter_value
      when "<="
        item <= filter_value
      ...
    end
  end
end

But if your working sets can get large, you're doing this big case statement 1000s or 1000000s of times for each operation even though it's going to take the same branch on every iteration. In my case the logic is much more involved than just a case statement, so the overhead is even worse. Instead, you can do it like this:

def process_filter(working_set,filter_type,filter_value)
  case filter_spec
    when "substring"
      def do_filter(item,filter_value)
        item.include?(filter_value)
      end
    when "equals"
      def do_filter(item,filter_value)
        item == filter_value
      end
    when "<="
      def do_filter(item,filter_value)
        item <= filter_value
      end
    ...
  end
  working_set.select {|item| do_filter(item,filter_value)}
end

Now the one-time branching is done once, up front, and the resulting single-purpose function is the one used in the inner loop.

In fact, my real example does three levels of this, as there are variations in the interpretation of both the working set and the filter value, not just the form of the actual test. So I build an item-prep function and a filter-value-prep function, and then build a do_filter function that uses those.

(And I actually use lambdas, not defs.)

Upvotes: 11

Chuck
Chuck

Reputation: 237010

Yes, there are. In fact, I'll bet you use at least one method that defines another method every day: attr_accessor. If you use Rails, there are a ton more in constant use, such as belongs_to and has_many. It's also generally useful for AOP-style constructs.

Upvotes: 5

Related Questions