Sorry, just realized I wasn't quite accurate on the "=== Hash" line.  It
should be something like:  new === {}
Since === compares its operands' classes, not one operand's class to the
other operand. Or more explicitly (and correctly):  new.is_a?(Hash)



On Thu, Dec 2, 2010 at 10:20 AM, Ben Hughes <[email protected]> wrote:

> Subclasses of Hash should be fine since === is liberal like is_a?, not
> instance_of?:
>
> Hash.new === HashWithIndifferentAccess.new  # => true
>
> It might be that you want this to work with objects that are not hashes at
> all, but are "hash-like".  Only problem there is that Hash#merge definitely
> assumes it's first argument responds to #to_hash and uses the result of
> that.  So actually, it might even be better to do:
>
> recursive_merge = lambda{|k, old, new| new.respond_to?(:to_hash) &&
> old.respond_to?(:to_hash) ? new.to_hash.merge(old, &recursive_merge) : new}
>
> Anyways obviously all of that is moot if you're working with pure hashes or
> subclasses thereof ;-)
>
> The Y Combinator stuff sounds interesting; if you come up with a Ruby
> solution using that, I'd love to see it!
>
>
>
> On Thu, Dec 2, 2010 at 9:41 AM, Matt Aimonetti <[email protected]>wrote:
>
>> Thanks for looking into that Ben. I agree that the bug you found could be
>> problematic and checking both sides is a good solution.
>>
>>
>> Also I think it's safer to just check directly for Hash instead of using
>>> respond_to?(:merge), as it's possible non-Hash objects will have defined a
>>> "merge" method:
>>> recursive_merge = lambda{|k, old, new| new === Hash && old === Hash ?
>>> new.merge(old, &recursive_merge) : new}
>>>
>>> This is exactly why I didn't reopen the Hash class, I do want to be able
>> to merge objects that are "mergeable" even tho they are not Hash instances.
>> One can subclass Hash for instance and now your code won't work :(
>>
>> I think this is actually a good candidate for the Y combinator
>> http://en.wikipedia.org/wiki/Y_combinator and I will give it a try.
>>
>> - Matt
>>
>>
>>
>> On Thu, Dec 2, 2010 at 8:40 AM, Ben Hughes <[email protected]> wrote:
>>
>>> I think that's the best approach, but there's a subtle bug there that
>>> might catch you depending on which hashes you're dealing with:
>>>
>>> What you have works fine when the nesting is identical such as in your
>>> example, but imagine if instead you have this:
>>>
>>> h1 = {:key => {a: 1, b: 2, c: {a: 1, b: 2}}}
>>> h2 = {:key => {c: {a: 'a', b: {x: 'y'}, c: 'c'}, d: 4}}
>>>
>>> (Note the addition of b: {x: 'y'} in the middle, so hash nesting at that
>>> point in h2 is one more than in h1)
>>>
>>> If you do h1.merge(h2, &recursive_merge) it will fail:
>>> TypeError: can't convert Fixnum into Hash
>>>
>>> This is because the lambda is running with "new" as {x: 'y'} (which does
>>> respond to merge) and "old" as 2, so it's calling {x: 'y'}.merge(2).
>>>  Interestingly the reverse operation (h2.merge(h1)) *would* work but
>>> naturally has a different result.
>>>
>>> So I think to be safe you need to do checking on both old and new:
>>>
>>> recursive_merge = lambda{|k, old, new| new.respond_to?(:merge) &&
>>> old.respond_to?(:merge) ? new.merge(old, &recursive_merge) : new}
>>>
>>> Also I think it's safer to just check directly for Hash instead of using
>>> respond_to?(:merge), as it's possible non-Hash objects will have defined a
>>> "merge" method:
>>>
>>> recursive_merge = lambda{|k, old, new| new === Hash && old === Hash ?
>>> new.merge(old, &recursive_merge) : new}
>>>
>>> It would be really nice if Ruby had some way to reference the defined
>>> lambda within the block so we didn't have to assign it to a local variable
>>> for use within the closure, something like:
>>>
>>> lambda{|k, old, new| new === Hash && old === Hash ? new.merge(old, &self)
>>> : new}
>>>
>>> But of course self references the object in which the lambda was defined,
>>> so that doesn't work.
>>>
>>>
>>> On Thu, Dec 2, 2010 at 12:07 AM, Matt Aimonetti <[email protected]
>>> > wrote:
>>>
>>>> Tonight I was trying to do some recursive merging of hashes and the only
>>>> decent solution I came up with looks like that:
>>>>
>>>> h1 = {:key => {a: 1, b: 2, c: {a: 1, b: 2}}}
>>>> h2 = {:key => {c: {a:'a', c: 'c'}, d: 4}}
>>>>
>>>> recursive_merge = lambda{|k, old, new| new.respond_to?(:merge) ?
>>>> new.merge(old, &recursive_merge) : new}
>>>> puts h1.merge(h2, &recursive_merge)
>>>> # => {:key=>{:a=>1, :b=>2, :c=>{:a=>"a", :b=>2, :c=>"c"}, :d=>4}}
>>>>
>>>> http://gist.github.com/724900
>>>>
>>>> (Yes I'm using Ruby 1.9, and if you aren't yet, I'm sorry for you ;) )
>>>>
>>>> The code above seems to be working pretty well but I'm wondering if
>>>> that's really the best way to do that, what do you guys think and can 
>>>> anyone
>>>> come up with a better solution?
>>>>
>>>> Thanks,
>>>>
>>>> - Matt
>>>>
>>>> --
>>>> SD Ruby mailing list
>>>> [email protected]
>>>> http://groups.google.com/group/sdruby
>>>
>>>
>>>  --
>>> SD Ruby mailing list
>>> [email protected]
>>> http://groups.google.com/group/sdruby
>>
>>
>>  --
>> SD Ruby mailing list
>> [email protected]
>> http://groups.google.com/group/sdruby
>>
>
>

-- 
SD Ruby mailing list
[email protected]
http://groups.google.com/group/sdruby

Reply via email to