The code:
class XHash
attr_accessor :value
def method_missing(mid, *args)
puts [["XHash::method_missing called", mid, args]]
self.value.send mid, *args
end
end
class Hash
def method_missing(mid, *args)
puts [["Hash::method_missing called", mid, args]]
str = mid.to_s
if str[-1] == '='
str = str[0, str.length - 1];
self[str]=args[0]
else
self[str]
end
end
end
v = XHash.new
v.value = Hash.new
puts [["Before: ", v.value]]
v.SomeKey = "SomeValue"
puts [["After: ", v.value]]
mRuby 3.0.0 output (expected):
[["Before: ", {}]]
[["XHash::method_missing called", :"SomeKey=", ["SomeValue"]]]
[["Hash::method_missing called", :"SomeKey=", ["SomeValue"]]]
[["After: ", {"SomeKey"=>"SomeValue"}]]
mRuby 3.1.0 output:
[["Before: ", {}]]
[["XHash::method_missing called", :"SomeKey=", ["SomeValue"]]]
[["Hash::method_missing called", :"SomeKey=", [nil]]]
[["After: ", {"SomeKey"=>nil}]]
Am I doing something illegal, or is it a bug? If it is already fixed, then what stable revision should I pick for the release of my project, so I don't storm users with bugfixes afterwards? I'm upgrading from mRuby 3.0.0 to 3.1.0, but it breaks .rb code in my gems.
PS: custom puts is used in my gem, so it behaves the same in both versions.
Related
It's just a simple question, how is y.<< method is able to halt the code-block mid execution ??
I have expected the code block to run only once and never halt in the middle :/
e = Enumerator.new do |y|
puts "Ruby"
y << 1
y << 2
puts "Ruby"
y << 3
end
puts e.each.next
puts e.each.next
puts e.each.next
e.rewind
puts e.each.next
puts e.each.next
puts e.each.next
Almost all Ruby implementations are Free Software and Open Source, so you can just look at the source code to see how it is implemented.
In Rubinius, the most interesting part is Enumerator::Iterator#reset, implemented in core/enumerator.rb:
#fiber = Fiber.new stack_size: STACK_SIZE do
obj = #object
#result = obj.each { |*val| Fiber.yield *val }
#done = true
end
and Enumerator::Iterator#next:
val = #fiber.resume
TruffleRuby's implementation is very similar, as you can see in src/main/ruby/truffleruby/core/enumerator.rb:
class FiberGenerator
# irrelevant methods omitted
def next
reset unless #fiber
val = #fiber.resume
raise StopIteration, 'iteration has ended' if #done
val
end
def reset
#done = false
#fiber = Fiber.new do
obj = #object
#result = obj.each do |*val|
Fiber.yield(*val)
end
#done = true
end
end
end
JRuby is also very similar, as you can see in core/src/main/ruby/jruby/kernel/enumerator.rb:
class FiberGenerator
# irrelevant methods omitted
def next
reset unless #fiber&.__alive__
val = #fiber.resume
raise StopIteration, 'iteration has ended' if #state.done
val
end
def reset
#state.done = false
#state.result = nil
#fiber = Fiber.new(&#state)
end
end
MRuby's implementation is very similar, as you can see in mrbgems/mruby-enumerator/mrblib/enumerator.rb.
YARV also uses Fibers, as can be seen in enumerator.c, for example here:
static void
next_init(VALUE obj, struct enumerator *e)
{
VALUE curr = rb_fiber_current();
e->dst = curr;
e->fib = rb_fiber_new(next_i, obj);
e->lookahead = Qundef;
}
static VALUE
get_next_values(VALUE obj, struct enumerator *e)
{
VALUE curr, vs;
if (e->stop_exc)
rb_exc_raise(e->stop_exc);
curr = rb_fiber_current();
if (!e->fib || !rb_fiber_alive_p(e->fib)) {
next_init(obj, e);
}
vs = rb_fiber_resume(e->fib, 1, &curr);
if (e->stop_exc) {
e->fib = 0;
e->dst = Qnil;
e->lookahead = Qundef;
e->feedvalue = Qundef;
rb_exc_raise(e->stop_exc);
}
return vs;
}
So, not surprisingly, Enumerator is implemented using Fibers in many Ruby implementations. Fiber is essentially just Ruby's name for semi-coroutines, and of course, coroutines are a popular way of implementing generators and iterators. E.g. CPython and CoreCLR also implement generators using coroutines.
One exception to this seems to be Opal. My assumption was that Opal would use ECMAScript Generators to implement Ruby Enumerators, but it does not look like that is the case. The implementation of Ruby Enumerators in Opal is found in opal/corelib/enumerator.rb, opal/corelib/enumerator/generator.rb, and opal/corelib/enumerator/yielder.rb with some help from opal/corelib/runtime.js, but unfortunately, I don't fully understand it. It does not appear to use either Ruby Fibers or ECMAScript Generators, though.
By the way, your usage of Enumerators is somewhat strange: you call Enumerator#each six times without a block, but calling Enumerator#each without a block just returns the Enumerator itself:
each → enum
Iterates over the block according to how this Enumerator was constructed. If no block and no arguments are given, returns self.
So, in other words, all those calls to Enumerator#each are just no-ops. It would make much more sense to just call Enumerator#next directly:
puts e.next
puts e.next
puts e.next
e.rewind
puts e.next
puts e.next
puts e.next
If I call the function anagrams below in irb, I get a non-empty hash container as expected. But if you comment out the print "No Key\n" line, the returned hash container is now empty. In fact for all elements in list the code in the elsif branch seems to execute. Either I'm going nuts or there is a nasty bug here:
def anagrams(list = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', 'scream'])
aHash = Hash.new()
list.each { |el|
aKey = el.downcase.chars.sort.to_a.hash
if aHash.key?(aKey)
# print "Has Key\n"
aHash[aKey] << el
elsif
# print "No Key\n"
aHash[aKey] = [el]
end
}
return aHash
end
I have the following versions of ruby and irb installed:
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux]
irb 0.9.6(09/06/30)
Your problem is that you're using an elsif where you mean else. This:
elsif
print "No Key\n"
aHash[aKey] = [el]
is misleading formatting, it is actually interpreted more like this:
elsif(print "No Key\n")
aHash[aKey] = [el]
but print returns nil so the logic is like this:
elsif(nil)
aHash[aKey] = [el]
and nil is false in a boolean context so aHash[aKey] = [el] never occurs. If you remove the print then you end up with this:
elsif(aHash[aKey] = [el])
and the assignment occurs; the assignment is also true in a boolean context (because an Array is) but the truthiness is irrelevant in this case.
You want to use else here:
if aHash.key?(aKey)
aHash[aKey] << el
else
aHash[aKey] = [el]
end
Even better would be to use a Hash with an Array (via a block) as its default value:
aHash = Hash.new { |h, k| h[k] = [ ] }
and then you don't need the if at all, you can just do this:
list.each do |el|
aKey = el.downcase.chars.sort.to_a.hash
aHash[aKey] << el
end
And you can use anything as a key in a Ruby Hash so you don't even need to .to_a.hash, you can simply use the Array itself as the key; furthermore, sort will give you an array so you don't even need the to_a:
list.each { |el| aHash[el.downcase.chars.sort] << el }
Someone will probably complain about the return at the end of your method so I'll do it: you don't need the return at the end of your method, just say aHash and it will be the method's return value:
def anagrams(list = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', 'scream'])
aHash = Hash.new { |h, k| h[k] = [ ] }
list.each { |el| aHash[el.downcase.chars.sort] << el }
aHash
end
You could also use each_with_object to compress it even more:
def anagrams(list = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', 'scream'])
list.each_with_object(Hash.new { |h, k| h[k] = [ ] }) do |el, h|
h[el.downcase.chars.sort] << el
end
end
but I'd probably do it like this to cut down on the noise:
def anagrams(list = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', 'scream'])
h = Hash.new { |h, k| h[k] = [ ] }
list.each_with_object(h) { |el, h| h[el.downcase.chars.sort] << el }
end
Given the following script, I see a different output using Ruby 1.8.7 and Ruby 1.9.2. My question is, what has changed in Ruby hashes that enforces this particular behavior?
def to_params(_hash)
params = ''
stack = []
_hash.each do |k, v|
if v.is_a?(Hash)
stack << [k,v]
else
#v = v.first if v.is_a?(Array)
params << "#{k}=#{v}&"
end
end
stack.each do |parent, hash|
hash.each do |k, v|
if v.is_a?(Hash)
stack << ["#{parent}[#{k}]", v]
else
params << "#{parent}[#{k}]=#{v}&"
end
end
end
params.chop! # trailing &
params
end
q = {"some_key"=>["some_val"], "another_key"=>["another_val"]}
n = convert_params(q)
puts n
Ruby 1.8.7 output:
some_key=some_val&another_key=another_val
Ruby 1.9.2 output:
some_key=["some_val"]&another_key=["another_val"]
1.9.2 retains the "Array" type of the value whereas 1.8.7 changes the type to string implicitly.
Two things have changed (the latter being your observation):
Hashes are ordered now
array.to_s used to return array.join, now it returns array.inspect (see 1.8.7 and 1.9.2).
def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
return item[:type]
end
}
}
end
result = get_type
puts "result: #{result}"
Expected output:
Checking foo.* against foo.txt
Found match: A
result: A
However, all I see is:
Checking foo.* against foo.txt
Found match: A
My current work around is this:
def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = []
x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
result << item[:type]
end
}
}
result[0] unless result.empty?
end
Why doesn't the first approach work? or maybe it is 'working', I just don't understand why I'm not getting what I'd expect.
May I suggest a refactor? your code looks kind of clunky because you are using each loops (imperative) when you in fact need a map+first (functional). As Ruby enumerables are not lazy this would be inefficient, so people usually build the abstraction Enumerable#map_detect (or find_yield, or find_first, or map_first):
def get_type_using_map_detect(name)
xs = [{:type => 'A', :patterns => ['foo.*']}, {:type => 'B', :patterns => ['bar.*']}]
xs.map_detect do |item|
item[:patterns].map_detect do |regex|
item[:type] if name.match(regex)
end
end
end
This is a possible implementation of the method:
module Enumerable
# Like Enumerable#map but return only the first non-nil value
def map_detect
self.each do |item|
if result = (yield item)
return result
end
end
nil
end
end
Works fine for me. Are you actually invoking it with
result = get_type puts "result: #{result}"
? Because that shouldn't work at all, though I'm assuming there's a linefeed that got eaten when you posted this.
I feel like I'm using Ruby the wrong way here: I want to generate all possible matches for the regular expression /[0-9A-Za-z]{3}/
I can't use succ because "999".succ => "1000" and "zZz".succ => "aaAa".
I'm having trouble using ranges because I can't seem to union (0..9), ('A'..'Z'), ('a'..'z')
So I wrote:
def alphaNumeric
#range and succ don't cut it for [0-9a-zA-Z]
(0..9).each{|x|yield x.to_s}
('a'..'z').each{|x|yield x}
('A'..'Z').each{|x|yield x}
end
def alphaNumericX3
alphaNumeric{ |a|
alphaNumeric{ |b|
alphaNumeric{ |c|
yield a+b+c
}
}
}
end
alphaNumericX3.each{|x|p x}
My question is 2 fold:
Is there a less ugly way, and is there a way where alphaNumericX3 could be defined from the parameters (alphaNumeric, 3)?
PS I'm aware that I could define a new class for range. But thats definitly not shorter. If you can make this next block shorter and clearer than the above block, please do:
class AlphaNum
include Comparable
attr :length
def initialize(s)
#a=s.chars.to_a
#length=#a.length
end
def to_s
#a.to_s
end
def <=>(other)
#a.to_s <=> other.to_s
end
def succ
def inc(x,n)
return AlphaNum.new('0'*(#length+1)) if x<0
case n[x]
when '9'
n[x]='A'
when 'Z'
n[x]='a'
when 'z'
n[x]='0'
return inc(x-1,n)
else
n[x]=n[x].succ
end
return AlphaNum.new(n.to_s)
end
inc(#length-1,#a.clone)
end
end
# (AlphaNum.new('000')..AlphaNum.new('zzz')).each{|x|p x}
# === alphaNumericX3.each{|x|p x}
Use Array#product:
alpha_numerics = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
alpha_numerics
.product(alpha_numerics, alpha_numerics)
.map { |triplet| triplet.join('') }
class String
def nextify
case self
when '9' then 'A'
when 'Z' then 'a'
when 'z' then '0'
else self.succ
end
end
end
class AlphaNum
def initialize(string)
#string = string
end
def succ
#string.split(//).inject("") { |s,n| s << n.nextify }
end
def method_missing(*args, &block)
#string.send(*args, &block)
end
end
a = AlphaNum.new("999")
puts a.succ #=> 'AAA'