I was impressed with these solutions. The use of an instance variable
to store the argument I found particularly clever.
I had worked on something like this a long time ago. At the time I
completely reimplemented Enumerable by hand to accept #each arguments
(it was the first time I ever used TDD, btw). I later realized
afterward a bit of meta-programming could make all of it a whole lot
easier, so I created what is now enumargs.rb (gem install enumargs).
It is similar to Benoit's solution.
require 'enumerator'
# This is a simple reimplementation of the core Enumerable module
# to allow the methods to take and pass-on arbitrary arguments to
the
# underlying #each call. This library uses Enumerator and scans
# Enumerable so it can alwasy stay in sync.
#
# NOTE Any Enumerable method with a negative arity cannot pass
arguments
# due to ambiguity in the argument count. So the methods #inject and
#zip
# do NOT work this way, but simply work as they do in Enumerable.
# However the method #find, and it's alias #detect, have been made
to work
# by removing its rarely used optional parameter and providing
instead an optional
# keyword parameter (:ifnone => ...). Please keep these difference
in mind.
#
# require 'enumargs'
#
# class T
# include Enumerable::Arguments
# def initialize(arr)
# @arr = arr
# end
# def each(n)
# arr.each{ |e| yield(e+n) }
# end
# end
#
# t = T.new([1,2,3])
# t.collect(4)
# #=> [5,6,7]
#
module Enumerable
module Arguments
def self.wrap_enumerable_method( methodname )
m = methodname
meth = Enumerable.instance_method(m)
arity = meth.arity
case arity <=> 0
when 0
class_eval %{
def #{m}( *args, &yld )
enum_for(:each, *args).#{m}( &yld )
end
}
when 1
class_eval %{
def #{m}( *args, &yld )
args, each_args = args[0...#{arity}], args[#{arity}..-1]
enum_for(:each, *each_args).#{m}( *args, &yld )
end
}
else
class_eval %{
def #{m}( *args, &yld )
enum_for(:each).#{m}( *args, &yld )
end
}
end
end
Enumerable.instance_methods(false).each do |m|
wrap_enumerable_method( m )
end
# Make exception for #find (a negative arity method) to accept
# keyword argument.
#
# ObjectSpace.find(Class, :ifnone=>lambda{1}) { |e| ... }
# ObjectSpace.find(Class, :ifnone=>lambda{1}) { |e| ... }
#
def find(*args, &yld) # future use **keys ?
if Hash === args.last and args.last.key?(:ifnone)
ifnone = args.last.delete(:ifnone)
args.pop if args.last.empty?
enum_for(:each, *args).find( ifnone, &yld )
else
enum_for(:each, *args).find( &yld )
end
end
alias_method :detect, :find
end
end
With that, the solution to #222 is simply:
class << ObjectSpace
include Enumerable::Arguments
alias each each_object
end
Eg.
ObjectSpace.select(Class){ |c| c < Exception }
Thanks.