ruby picture

RCR 305: return nil from simple object loops

Submitted by eric_mahurin (Fri May 13 17:47:19 UTC 2005)

Abstract

Have simple loop methods that operate (read-only) on object and return that object return nil instead. When break(value) is used inside the loop it will yield an expression that results in the break value or nil rather than the break value or the object.

Problem

There are many times you wish to iterate over something (an enumeration, number in a range (up, down, step), pattern matches, etc) and possibly terminate the loop under some condition. You'll want to get some information upon termination or know if the loop fell through normally. Ruby has many object loops that do the iteration part just fine - each, each_with_index, upto, downto, etc. Ruby has the nice feature that "break" accepts a value that is returned by the built-in loops and also the object loops being discussed. Built-in loops return nil when they exit normally so that the "no break" condition can easily be detected. The simple object loops on the other hand return the original object on the "no break" condition and thus may not necessarily be distinguished from the break condition (break could return the same object).

Proposal

For all of the loop methods being described except "each" (and thus the built-in "for"), change them to return nil. This should just be a matter of removing the "self" after the main loop in the method. Here are the methods I'm talking about (I may have missed some):

 downto
 #each - see analysis
 each_byte
 each_index
 each_key
 each_line
 each_pair
 each_value
 each_with_index
 reverse_each
 scan
 step
 times
 upto

Analysis

Within the 1.8.2 library, here are number of simple loops (object methods and built-in) I found:

 .downto => 5
 .each => 1091
 .each_byte => 19
 .each_index => 10
 .each_key => 5
 .each_line => 4
 .each_pair => 5
 .each_value => 17
 .each_with_index => 23
 .reverse_each => 17
 .scan => 79
 .step => 14
 .times => 32
 .upto => 28
 for => 272
 loop => 61
 until => 96
 while => 375

Of the method loops above, I found (with a script that included multi-line brace matching) only 3 instances of where the current return value is being used:

 ./optparse.rb: RCSID = %w$Id: optparse.rb,v 1.40.2.4 2004/12/05 10:39:58 nobu Exp $[1..-1].each {|s| s.freeze}.freeze
 ./optparse.rb: parse(*IO.readlines(filename).each {|s| s.chomp!})
 ./rexml/element.rb: prefix = attributes.prefixes.each { |prefix| return "#{prefix}:" if namespace( prefix ) == namespace } || ''

These remaining 2 cases could all be re-written easily and they represent only 0.2% of the "each" calls (and even less if you include "for"). The return values of none of the other methods being discussed was used. Because the return value of "each" is used some, changing its functionality is an optional part of this RCR.

To give an idea of the usefulness, none of the element search methods (i.e. find, include?, index, and variations) would really be needed as they could be written with 1-liners (not much longer and more descriptive):

 #enum.find{|o|...}
 enum.each{|o|break(o) if ...}
 #enum.include?(x)
 enum.each{|o|break(true) if o==x}
 #array.index(x)
 array.each_with_index{|o,i| break(i) if x==o}

In the above, each_with_index could be used instead of "each" if "each" is not changed (but each_with_index is).

Unfortunately when one of the standard methods doesn't fit a certain search across a group of things, it becomes easiest to use loop/while/until instead of the loop methods (the Ruby way). When you want to find something by iterating over any group of things that are not "Enumerated" (i.e. not "each" or "each_index"), you are even more out of luck since no find-type methods are there.

Implementation

 def <loop-method>(...,&block)
     ...
     <while|until|loop do>
         ...
         block[...]
         ...
     end
     # "self" was here - remove it
     # everything else should be the same
     # just return the natural thing - what the loop would have
 end
ruby picture
Comments Current voting
If you want different behaviour, create a new method. Do not use #each and #break to find something, use or create a method #find instead. Etc. -esaynatkari


I agree with the above comment. It seems like you want to go back to a much more raw, do-it-yourself interface to enumerables, whereas I like having a variety of methods.

In particular, when you say:

  In the above, each_with_index could be used instead of "each" if    
  "each" is not changed (but each_with_index is).

you're conjuring up the possibility of a lot of unused index counters, and generally a feeling that there's a workaround for a problem -- but the workaround has really created the problem, in my view.

David Black


The examples above just point out the possibilities this change would offer (a more useful example would be the search for object equality). I think it also promotes better coding style, one of the most important aspects of Ruby. --Michel Martens


I would agree if I was just talking about "each" and "each_with_index". We could easily add a couple more generally useful finding methods to Enumerable (maybe this should be done anyways):

    def search(&block)
        self.each{|o|found=block[o];return(found) unless found.nil?}
        nil
    end
    def search_with_index(&block)
        self.each_with_index {|o,i|found=block[o,i];return(found) unless found.nil?}
        nil
    end

but... what about all of the other each-like routines that Enumerable has no access to. Doing find-like functions across what they iterate is even more of a pain. Take the m.step(n,s) method for example. What use is it for this function to return m? But, having it return nil would be.


Actually, my example search/search_with_index would probably be served with "if found" instead of "unless found.nil?" since comparisons return true/false and not true/nil. With that change, you could emulate find/include?/index with these:

 find{|o|condition}: search{|o|condition && o}
 include?(x): search{|o|x==o && true}
 index(x): search_with_index{|o,i|x==o && i}

I don't want this to distract from my main proposal though. Only each and each_with_index can take advantage of this. The others listed have no good finding abilities without making this change.


  I think it also promotes better coding style, one of the most
  important aspects of Ruby. --Michel Martens

We all agree that good style is good and important.

My problem here is that all I'm seeing are examples that are verbose and "raw" (in the sense of exposing loop logic rather than encapsulating it in appropriately-named methods).

I'm afraid I just don't understand the attraction of this:

 array.each_with_index{|o,i| break(i) if x==o}

as a replacement candidate for this:

 array.index(o)

Even if this *could* be done with the nil return values, why would one want to do it? How does this enhance Ruby -- stylistically, semantically, expressively, etc.?


Just a thought: Maybe your #search functionality could be had by letting #select take an optional argument specifying the maximum number of elements. If you only wanted one element (at most), then #select could skip the array creation and just return the element or nil.

As for the whole #each thing, I don't see the point. I don't think it's particularly useful for #each (and the other loop methods) to return self, but I don't see why returning nil would be more useful.

Myself, I sometimes do this:

 class NotFoundError < StandardError ; end
 module Enumerable
   def find! &block
     find(lambda { raise NotFoundError }, &block)
   end
 end

But I don't know if that's relevant, as I honestly don't see the point of this proposal. Voted opposed, just to be difficult.


Strongly opposed 12
Opposed 12
Neutral 12
In favor 12
Strongly advocate 12
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .