Symbols vs. strings as hash_keys - interchangeable or not?

W

Wes Gamble

All,

I have a Rails app where I load a YAML file into a class variable (in my
application.rb file so it's available to all controllers), like so:

@@config = YAML.load_file("#{RAILS_ROOT}/config/eSimplyConf.yml")

and I created a method to give me back this hash:

def config
@@config
end

However, I notice that when I call config[:key_name], I don't get
anything, but when I call config['key_name'] I do get the value. I
further verified that if I called config[:key_name.to_s], I will get the
value, thus underscoring the distinction between symbol and string here
as the type of key_name.

I was under the impression that symbols and strings were interchangeable
for the purpose of addressing a hash element. Is this not the case? Is
my problem that YAML.load_file generates keys that are strings instead
of symbols, and of course, I'm using Rails, so that I expect all of my
hash keys to be symbols :)?

Thanks,
Wes
 
E

Ezra Zygmuntowicz

All,

I have a Rails app where I load a YAML file into a class variable
(in my
application.rb file so it's available to all controllers), like so:

@@config = YAML.load_file("#{RAILS_ROOT}/config/eSimplyConf.yml")

and I created a method to give me back this hash:

def config
@@config
end

However, I notice that when I call config[:key_name], I don't get
anything, but when I call config['key_name'] I do get the value. I
further verified that if I called config[:key_name.to_s], I will
get the
value, thus underscoring the distinction between symbol and string
here
as the type of key_name.

I was under the impression that symbols and strings were
interchangeable
for the purpose of addressing a hash element. Is this not the
case? Is
my problem that YAML.load_file generates keys that are strings instead
of symbols, and of course, I'm using Rails, so that I expect all of my
hash keys to be symbols :)?

Thanks,
Wes


Wes-

The symbol and string as hash key are specific to rails
implementation of HashWithIndifferetnAccess. Thi is a hash that
responds to string and symbol keys in the same way. This is not the
behavior of the standard ruby Hash class. If you want this behavior
then make your config hash be a HashWithIndifferentAccess instead of
a normal hash. You can do this by changinf the header in your yaml
file to this:

--- !map:HashWithIndifferentAccess
foo: bar
bar: baz


-Ezra
 
W

Wes Gamble

Ezra,

Is it done in Rails like this for performance reasons (I'm guessing
symbols may be slightly more lightweight than Strings)?

Thanks,
Wes
 
C

Cam

Hey,

The symbol and string as hash key are specific to rails
implementation of HashWithIndifferetnAccess. Thi is a hash that
responds to string and symbol keys in the same way. This is not the
behavior of the standard ruby Hash class. If you want this behavior
then make your config hash be a HashWithIndifferentAccess instead of
a normal hash. You can do this by changinf the header in your yaml
file to this:

I've actually been wondering about this myself lately.. (just started
playing w/ rails for a webapp i had to write a couple days ago). What
is the point of using symbols for the hash keys? Is that just an
aesthetic thing? or does it have to be this way?

Thanks,
Cam
 
T

transfire

Wes said:
All,

I have a Rails app where I load a YAML file into a class variable (in my
application.rb file so it's available to all controllers), like so:

@@config = YAML.load_file("#{RAILS_ROOT}/config/eSimplyConf.yml")

and I created a method to give me back this hash:

def config
@@config
end

However, I notice that when I call config[:key_name], I don't get
anything, but when I call config['key_name'] I do get the value. I
further verified that if I called config[:key_name.to_s], I will get the
value, thus underscoring the distinction between symbol and string here
as the type of key_name.

I was under the impression that symbols and strings were interchangeable
for the purpose of addressing a hash element. Is this not the case? Is
my problem that YAML.load_file generates keys that are strings instead
of symbols, and of course, I'm using Rails, so that I expect all of my
hash keys to be symbols :)?

As Ezra points out they are not the same in Ruby. A hash key can be any
object whatsoever. It could even be a number or another hash:

{ {:a=>1} => 'yep' }

Since symbols and strings are not the same thing, they likewise do not
represent the same keys. Versitle Ruby's hash is. Common it is not.
Lets be honest, 80% of the time, if not more we simply want a "keyword"
representation for out hash keys. Ruby's hash does not make that
straigiht foward and no doubt we've all spent too much time fusing with
to_s and to_sym in this regard.

I've offered some ideas for improvement in this area, symbol to string
coercion for instance. But that seems to be just as problematic. My
last consideration was a way to "normalize keys on the fly"

h = Hash.new.key_by{ |k| k.to_sym }
h["x"] = 1
h #=> {:x=>1}

I point out one last issue in this regard though, is that I have found
that I generally want my keys as symbols in code, but when I dump or
load YAML I want them as strings. Two extra time-consuming steps to do
this, so I end up just blowing wads of memory and sticking with
strings. :(

Anyhow sorry to ramble on. But I would be interested it what others
have thoughtof for dealing with this.

As a solution in your case you might be interested in what I often do,
use Facets OpenCascade. It can load simple YAML configs into a nice
method-based access object:

s = %{
---
a: "a"
b:
x: "x"
}

oc = OpenCascade.new( YAML.load(s) )

oc.a #=> "a"
oc.b.x #=> "x"

T.
 
S

Sean O'Halpin

As Ezra points out they are not the same in Ruby. A hash key can be any
object whatsoever. It could even be a number or another hash:

{ {:a=>1} => 'yep' }
Not meaning to be picky ;) but your example is a bit misleading. Consider:

h1 = { {:a => 1} => 'nope'}
p h1[{:a => 1}]
#=> nil
hk = {:a => 1}
h2 = {hk => 'yep'}
p h2[hk]
#=> "yep"

The problems of using hashes as keys has come up a few times - see
e.g. ruby-talk:167185 for one solution. Or you could just use:

class Hash
def hash
self.to_a.hash
end
def eql?(other)
hash == other.hash
end
end

h = {{ :a => 1, :b => 2 } => 'yep'}
p h[:a => 1, :b => 2]
#=> "yep"

though to be honest I'm not sure if this has unpleasant side-effects
on any libs.

Regards,
Sean
 
T

Trans

Sean said:
As Ezra points out they are not the same in Ruby. A hash key can be any
object whatsoever. It could even be a number or another hash:

{ {:a=>1} => 'yep' }
Not meaning to be picky ;) but your example is a bit misleading. Consider:

h1 = { {:a => 1} => 'nope'}
p h1[{:a => 1}]
#=> nil
hk = {:a => 1}
h2 = {hk => 'yep'}
p h2[hk]
#=> "yep"

The problems of using hashes as keys has come up a few times - see
e.g. ruby-talk:167185 for one solution. Or you could just use:

Huh? I didn't know that. Why isn't one hash "eql?" to another that has
"eql?" elements?

T.
 
W

Wes Gamble

Not to be dense, but why would you use a hash as a hash key in the first
place?

I guess it could "save you" another level of indirection, but I would
think the code would be so confusing as to not be worth it.

Wes
 
S

Sean O'Halpin

As Ezra points out they are not the same in Ruby. A hash key can be any
object whatsoever. It could even be a number or another hash:

{ {:a=>1} => 'yep' }
Not meaning to be picky ;) but your example is a bit misleading. Consider:

h1 = { {:a => 1} => 'nope'}
p h1[{:a => 1}]
#=> nil
hk = {:a => 1}
h2 = {hk => 'yep'}
p h2[hk]
#=> "yep"

The problems of using hashes as keys has come up a few times - see
e.g. ruby-talk:167185 for one solution. Or you could just use:

Huh? I didn't know that. Why isn't one hash "eql?" to another that has
"eql?" elements?

T.

Because Hash#hash == Hash#object_id and st_lookup() uses this and eql?
to test for equality (or rather inequality). At least, that's my
understanding of the relevant code in st.c:

#define PTR_NOT_EQUAL(table, ptr, hash_val, key) \
((ptr) != 0 && (ptr->hash != (hash_val) || !EQUAL((table), (key), (ptr)->key)))

where EQUAL is defined as:

#define EQUAL(table,x,y) ((x)==(y) || (*table->type->compare)((x),(y)) == 0)

I agree that it does seem a little counter-intuitive to not have
Hash#eql? compare by value (like Array#eql? does).

Regards,
Sean
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top