-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 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
on 24.10.2009 19:44
on 27.10.2009 00:23
> 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 27.10.2009 09:10
On Saturday 24 October 2009, Daniel Moore 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
on 27.10.2009 11:16
2009/10/27 Stefano Crocco <stefano.crocco@alice.it>: > 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
on 27.10.2009 11:22
On Tuesday 27 October 2009, Robert Klemme wrote: > |Instance variables are not thread safe. > | Yes, I was aware of this problem but forgot to mention it in my solution. Stefano
on 28.10.2009 10:14
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 01.11.2009 21:27
On Oct 28, 4:13 am, Benoit Daloze <erego...@gmail.com> 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?
on 01.11.2009 21:52
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.
on 04.12.2009 08:01
(Sorry for the delays!)
There were many great solutions to this week's quiz.
James Coglan 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 Daloze'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 Klemme 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 Crocco and Intransition also gave some good solutions, view
the attachment[1] 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!
[1]: http://rubyquiz.strd6.com/quizzes/222.tar.gz