[QUIZ] Enumerable ObjectSpace (#222)

D

Daniel Moore

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

The three rules of Ruby Quiz:

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 Quiz 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 Talk 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 Trans[1].

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!


[1]: http://rubyworks.github.com
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]
## Enumerable ObjectSpace (#222)

Kaixo Rubyists,

This week's quiz was suggested by Trans[1].

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.



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 }
 
S

Stefano Crocco

|
|## Enumerable ObjectSpace (#222)
|
|Kaixo Rubyists,
|
|This week's quiz was suggested by Trans[1].
|
|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!
|
|
|[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
 
R

Robert Klemme

2009/10/27 Stefano Crocco said:
On Saturday 24 October 2009, Daniel Moore 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
 
B

Benoit Daloze

[Note: parts of this message were removed to make it a legal post.]

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?, :eek:ne?, :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
 
I

Intransition

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

Intransition

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top