From: Robert Klemme Date: 2010-10-07T16:08:01+09:00 Subject: Re: "map" a deeply nested structure: Object#deep_map On Wed, Oct 6, 2010 at 6:05 PM, Guido De Rosa wrote: >> What about keys?  Hash#map allows to map keys and values >> >> irb(main):001:0> h={1=>2,3=>4} >> => {1=>2, 3=>4} >> irb(main):002:0> h.map {|k,v| [k*10,v*100]} >> => [[10, 200], [30, 400]] >> >> What, if the key is an Array or Hash?  Wouldn't this be more >> appropriate? >> >>            out[k.deep_map(&block)] = v.deep_map(&block) > > I was not interested in mapping keys, but this would be a reasonable > extra feature. OTOH you say you want to mimic #map behavior and since this is part of the standard behavior people might expect to be able to map keys as well if this goes into a library. > So deep_map would be used like that: > >    object.deep_map{|k, v| ... [result_key, result_value]} > > resembling Hash#map behavior > > and when object is not Hash-like, result_key would be simply ignored. > > It should not be hard to allow client code to use also "the previous > API": > >    object.deep_map{|x| ... } > > by checking block.arity in the implementation code. I think that's not necessary. You can just pass an Array to get the same behavior as Hash#each: irb(main):002:0> def f1; yield [1,2] end => nil irb(main):003:0> f1 {|a| p a} [1, 2] => [1, 2] irb(main):004:0> f1 {|a,b| p a} 1 => 1 irb(main):005:0> {1=>2}.each {|a,b| p a} 1 => {1=>2} irb(main):006:0> {1=>2}.each {|a| p a} [1, 2] => {1=>2} irb(main):007:0> >> You could remove lines "out = ..." and replace them by >> >> out = self.class.new > > Good point... apparently! > > What if self is a Range? Good point. > Forcing to return an Array (or a Hash) is not so bad: even the standard > method Enumerable#map does so! And so does Enumerable#sort and many > others: it's just to avoid nonsensical situations :-) But according to that logic *all* collections returned should be Arrays. So out = {} would become out = []. >> I wonder how many use cases for this there are actually.  In your >> example you can uniformly treat each object since you want the >> object_id.  That would work for a few other methods as well (e.g. >> #inspect, #to_s).  In other cases you would have to discriminate >> treatment of leave values in your block, e.g. >> >> x.deep_map |y| >>   case y >>   when String >>     ... >>   when Fixnum >>    ... >>   else >>    ... >> end >> >> That list could become lengthy.  Basically you implemented tree >> traversal with double dispatch - only that the double dispatch must be >> done manually in the block. :-) > > The same holds for standard Enumerable#map and Hash#map, so... > > I just want to write the recursive version of some Ruby core methods, > resembling by many aspects pretty much the same behavior, including the > fact that some checks are up to the user ;-) The difference is that in a Hash or Array values are usually uniform while with a recursive structure it is much more likely that they are not. So in the case of Enumerable#map you typically know what objects you map while in the recursive case you rather need checks. I still wonder about the usability of this. Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/