Rick,
Good points - however just overriding eg. Hash#[], Hash#[]= &&
Hash#key? also usually
implies having to alias related methods to the new implementation as a
result of their definition referencing
the original method by default :
rb_define_method(rb_cHash,"include?", rb_hash_has_key, 1);
rb_define_method(rb_cHash,"member?", rb_hash_has_key, 1);
rb_define_method(rb_cHash,"has_key?", rb_hash_has_key, 1);
rb_define_method(rb_cHash,"has_value?", rb_hash_has_value, 1);
rb_define_method(rb_cHash,"key?", rb_hash_has_key, 1);
rb_define_method(rb_cHash,"value?", rb_hash_has_value, 1);
An example from ActiveSupport :
# Checks the hash for a key matching the argument passed in:
#
# hash = HashWithIndifferentAccess.new
# hash["key"] = "value"
# hash.key? :key # => true
# hash.key? "key" # => true
#
def key?(key)
super(convert_key(key))
end
alias_method :include?, :key?
alias_method :has_key?, :key?
alias_method :member?, :key?
I recall having spent some dead time in the past being either bitten
by this on my own projects or using some gems / frameworks that went
down this road
and exclusively tested against the API style of the author(s) eg. key?
VS has_key? etc.
From a library / framework perspective, missing an alias is usually
very dangerous as you can't assume a usage style and need to support
all of the current
Hash API in order to be compliant.
Perhaps a unified sub project, much like rack would be a good start -
released as a gem.The hwia gem implementation was just a proof
of concept, but I found having to duplicate a lot of core hash methods
for not being exposed via intern.h somewhat of a pain, in addition to
supporting both
mainline ruby versions.Having more methods exposed via intern.h would
help quite a bit with a lean gem implementation.
Exposing the hashing API to library authors also removes the
dependencies of having to subclass methods and restoring method
aliases - adhere to
a well defined contract ( #hash(a), #compare(a,b) or whatever it may
be, or whatever such a scheme may be called ) and the expected
interfaces and behavior
holds for the core Hash API.
Thoughts ?
- Lourens
On 2009/11/07, at 18:21, Rick DeNatale wrote:
> I'm not sure that it really makes sense to add any of this to core.
>
> So far the argument goes something like this.
>
> Several important ruby based web frameworks have some form of
> HashWithIndifferentAccess class, Rails, Merb and Sinatra have been
> mentioned. Did I miss any?
>
> Since it seems to be a common use case, perhaps it should be in Ruby
> core.
>
> And implementations have been given which either:
>
> store keys in the hash which are the same type as that given as
> the key parameter to WhateverTheHeckWeCallThisThing#[]=
> convert the key to a symbol if it's a string
> convert the key to a string if it's a symbol.
>
> Then the arguments start over what to name it.
>
>
> So I've just now looked at Merb, Rails and Sinatra to see what's up.
>
> The ONLY difference between Merb and Rails here, as far as I can tell
> is the name of WhateverTheHeckWeCallThisThing. Rails (actually
> ActiveSupport) calls it HashWithIndifferent access, Merb (actually the
> extlib gem) calls it Mash. Other than the name the code appears to be
> identical, including the initial comment:
>
> # This class has dubious semantics and we only have it so that
> # people can write params[:key] instead of params['key']
> # and they get the same value for both keys.
>
> And what is the implementation? Basically it
>
> 1) overrides the initialize method to convert its keys to strings
> after being initialized if the parameter to new is a hash.
>
> 2) overrides Hash#default(key=nil) to efffectively return
> self[key.to_s] if the key arg is a symbol and key.to_s is included in
> the hash.
>
> 3) overrides Hash#[]=(key,value) to effectively call
> super[convert_key(key)] = convert_value(value)
> where convert_key changes symbols to strings and leaves any
> other key alone
> and convert_value converts an ordinary hash value to a
> HashWithIndifferentAccess/Mash and leaves anything else alone. This
> is because one of the main uses of this thing is for nested parameters
> in a http request.
>
> 4) overrides any other method which takes a key argument to call
> convert_key on it before using it.
>
> Sinatra, is much more minimal, as one might expect. It doesn't have
> such a class, rather it defines a Sinatra::Base#indifferent_hash
> method
>
> def indifferent_hash
> Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
> end
>
> Which simply makes
>
> indifferent_hash[:a]
> return the same value as
> indifferent_hash['a']
> if :a isn't a key in the hash.
>
> Sinatra apparently relies on the code which uses these hashes not to
> use both a symbol and it's string form together for []=
>
>
> Personally I think that as implemented in both Rails/Merb and Sinatra,
> this is much ado about nothing, It wouldn't really buy much to pull
> this code down to core whatever you name it, and if something with
> different semantics, such as some of the proposals on this thread was
> adopted, it's likely that the frameworks, would ignore it, unless it
> were given a name which clashed.
>
> As for the decision made by all three of these frameworks to
> consistenly use strings rather than symbols for this, I think it makes
> sense. When I first realized that HashWIthIndifferentAccess did it
> this way it didn't make sense to me, since I perceived the motivation
> to be performance and one a symbol was interned looking up symbol keys
> should be faster since Symbol#hash is O(1), and String#hash is
> O(string.length).
>
> But doing it the other way, and using Symbols rather than Strings for
> the actual, stored, keys in the hash has a time and space cost,
> because every time a String is used as a key in a method, it must be
> converted to a Symbol, which requires the equivalent of looking it up
> to see if it has already been interned, and making a copy non-gcable.
>
> And taking a laissez-faire approach and storing either strings or
> symbols and trying to make them come out the same when the Hash is
> accessed, would seem to require additional overhead for each lookup,
> as well as introducing ambiguities of specification if both :key and
> 'key' were used to store in the hash, as I pointed out earlier in this
> thread.
>
> To summarize, I'd vote for leaving things in core as they are, and
> letting frameworks implement such extensions the way they see fit.
> --
> Rick DeNatale
>
> Blog: http://talklikeaduck.denhaven2.com/
> Twitter: http://twitter.com/RickDeNatale
> WWR: http://www.workingwithrails.com/person/9021-rick-denatale
> LinkedIn: http://www.linkedin.com/in/rickdenatale
>