Enumerable ObjectSpace (#222)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this
    quiz until 48 hours have elapsed from the time this message was
    sent.

  2. Support Ruby Q. by submitting ideas and responses
    as often as you can.

  3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby T. follow the discussion. Please reply to
the original quiz message, if you can.


RSS Feed: http://rubyquiz.strd6.com/quizzes.rss

Suggestions?: http://rubyquiz.strd6.com/suggestions

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Enumerable ObjectSpace (#222)

Kaixo Rubyists,

This week’s quiz was suggested by Trans1.

ObjectSpace has the method #each_object. I’ve always wanted to alias
that to #each and make ObjectSpace Enumerable. But there’s a catch,
#each_object takes an argument and Enumerable does not pass arguments
on to the #each method. So how does one resolve this issue? What’s the
easiest way to make ObjectSpace Enumerable?

One could always take the brute force approach and completely
re-implement Enumerable module to pass along the arguments. But I’m
hoping there are much more elegant solutions to be had.

Have fun!

easiest way to make ObjectSpace Enumerable?

One could always take the brute force approach and completely
re-implement Enumerable module to pass along the arguments. But I’m
hoping there are much more elegant solutions to be had.

There is probably something better to be had using reflection and method
generation, but here’s one stab:

class << ObjectSpace
def method_missing(sym, args, &block)
mod = args.first.is_a?(Module) ? args.shift : nil
enum = enum_for(
[:each_object, mod].compact)
enum.send(sym, *args, &block)
end
end

e.g. puts ObjectSpace.map(Class) { |c| c.name }

On Saturday 24 October 2009, Daniel M. wrote:

|on to the #each method. So how does one resolve this issue? What’s the
|easiest way to make ObjectSpace Enumerable?
|
|One could always take the brute force approach and completely
|re-implement Enumerable module to pass along the arguments. But I’m
|hoping there are much more elegant solutions to be had.
|
|Have fun!
|
|
|[1]: http://rubyworks.github.com

Here’s my solution. It uses an instance variable to keep trace of the
argument
given to the enumerable method.

In more detail, here’s how it worsk: after extending Enumerable, in
redefines
each method added by it allowing any number of arguments (indeed, all
that is
needed is to add one argument. I believe it can be done that way but I
don’t
think it’s worth the effort). The first argument is then removed from
the
array and inserted at the end of the array contained in the
@__chosen_class
instance variable (which is created if it doesn’t already exist). Then,
super
is called, passing it the new argument list (that is, the one with the
first
argument removed) and the block.

The each method is then defined to either accept one argument, so that
you can
do

ObjectSpace.each(Array){…}

or to use the last element of @__chosen_class.

The reason to store the class in an array is to allow nested calls, that
is
something like this:

ObjectSpace.select(cls1) do |o|

ObjectSpace.any?(cls2){…}

end

When ObjectSpace.any? is called, it’ll push cls2 at the end of
@__chosen_class, so that its each will use that class, while each for
the
outer block will use cls1. I enclosed the call to super in a
begin/ensure
clause to be sure that the class pushed in the array is removed at the
end of
the method call.

module ObjectSpace

methods = Enumerable.instance_methods - self.public_methods

extend Enumerable

methods.each do |m|

instance_eval <<-STR

  def #{m} *args, &blk
    @__chosen_class ||= []
    @__chosen_class << args.shift
    begin super *args, &blk
    ensure @__chosen_class.pop
    end
  end

STR

end

def self.each cls = nil
each_object(cls || @__chosen_class[-1]){|o| yield o}
end

end

Stefano

2009/10/27 Stefano C. [email protected]:

On Saturday 24 October 2009, Daniel M. wrote:
Here’s my solution. It uses an instance variable to keep trace of the argument
given to the enumerable method.

Instance variables are not thread safe. I am using a thread local
instead:

#! ruby19

CODE

class <<ObjectSpace
include Enumerable

def arg(a, &b)
stack = Thread.current[:os_arg] ||= []
stack.push a

begin
  instance_eval(&b)
ensure
  stack.pop
end

end

def each(&b)
a = ((Thread.current[:os_arg].last || Object) rescue Object)
each_object(a, &b)
self
end
end

TEST

Foo = Struct.new :x
foos = (1…10).map {|i| Foo.new i}

result_test = ObjectSpace.arg Foo do
p map {|x| x.inspect}

ObjectSpace.arg String do
string_count = inject(0) do |sum, o|
raise “no string” unless String === o
sum + 1
end

puts "Nested String count: #{string_count}"

end

map {|x| “rt #{x.inspect}”}
end

p result_test

Thread.new do
p ObjectSpace.inject(0) {|sum,| sum + 1}
end.join

ObjectSpace.arg Foo do
p map {|x| x.class}
end

Cheers

robert

On Tuesday 27 October 2009, Robert K. wrote:

|Instance variables are not thread safe.
|

Yes, I was aware of this problem but forgot to mention it in my
solution.

Stefano

Hi, here is my try:

module ObjectSpace
extend Enumerable

def each(mod = nil, &block)
    if block_given?
        if mod.is_a? Module
            each_object(mod) { |o| block.call(o) }
        else
            each_object { |o| block.call(o) }
        end
    else
        to_enum
    end
end
module_function :each

(Enumerable.instance_methods-[:to_enum]).select { |m|

self.method(m).arity == 0 }.each { |m|
instance_eval <<-STR
def #{m}(mod = nil, &block)
if mod.is_a? Module
objects = to_enum.select { |o| o.is_a? mod }
else
objects = to_enum
end
objects.send(:#{m}, &block)
end
STR
}
end

Some tests:

p ObjectSpace.each #=> #<Enumerator: ObjectSpace:each>
p ObjectSpace.map(Numeric) { |o| o } #=> [2.718281828459045,
3.141592653589793, …]

p ObjectSpace.map(Class) { |c| c.name }.select { |o| o.to_s[0]<“E” } #=>
[“Complex”, “Binding”, …]
p ObjectSpace.select(Class) { |o| o.to_s[0]<“E” }.map { |c| c.name } #=>
[“Complex”, “Binding”, …]

I’m wondering which method should implement an arg. For this I used:
(Enumerable.instance_methods-[:to_enum]).select { |m|
self.method(m).arity
== 0 }
#=> [:to_a, :sort, :sort_by, :find_all, :select, :reject, :collect,
:map,
:partition, :group_by, :all?, :any?, :one?, :none?, :min, :max, :minmax,
:min_by, :max_by, :minmax_by, :reverse_each, :take_while, :drop_while]

I think the main interest is for each and map, maybe select because it
looks
nice.

For the others, it seem more clear to me to do: ObjectSpace.select {|o|
o.is_a? mod}

Something I don’t understand is why it seem impossible to modify each if
Enumerable is included

Cheers

On Oct 28, 4:13 am, Benoit D. [email protected] wrote:

Something I don’t understand is why it seem impossible to modify each if
Enumerable is included

Not sure what you mean. Could you explain further?

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.

(Sorry for the delays!)

There were many great solutions to this week’s quiz.

James C. provides us with a compact solution using method_missing.

class << ObjectSpace
  def method_missing(sym, *args, &block)
    mod  = args.first.is_a?(Module) ? args.shift : nil
    enum = enum_for(*[:each_object, mod].compact)
    enum.__send__(sym, *args, &block)
  end
end

When calling an Enumerable method like map or each on
ObjectSpace it will trigger method_missing as James’s solution
does not have ObjectSpace include the Enumerable methods directly.
The first argument, if present and a Module will be extracted. An
enumerator is then created using enum_for and passing the given
Module, if present, to the each_object method of ObjectSpace.
The original method invoked that brought us into method missing in
then invoked on the new enumerator with the remaining arguments and
block. This will work for map as well as inject as long as the
first argument is a Module. Methods such as map and each may be
called with no Module given, in which case the block is called once
for each living, nonimmediate object in the Ruby process. The density
of meta-programming in James’s solution is a paragon of virtue and a
good test of one’s meta-programming knowledge.

Benoit D.'s solution uses meta-programming to define new methods
that accept a Module parameter for all the 0 arity methods in
Enumerable. each is also redefined to accept a Module parameter,
though this is not used by the redefined methods, they use to_enum
and select to filter out all instances of the given Module.

objects = to_enum.select { |o| o.is_a? mod }

Robert K. added an arg method to ObjectSpace which stored the
parameter in a thread local stack variable, so that calls may be
nested. Robert’s arg method takes a block which is evaluated within
the context of ObjectSpace. The each method is defined to call
each_object with the parameter given by the top of the thread local
stack variable.

Stefano C. and Intransition also gave some good solutions, view
the attachment1 for full details.

Thank you everyone for you solutions to this week’s quiz. It was fun
and really mind stretching. Keep the suggestions coming!