Q:

Why would you want to use an array, or hash as hash key in ruby?

i'm using Ruby 1.9.3
I figured out that you can use an array, or a hash as hash key in ruby:
h = Hash.new

h[Array.new] = "Why?"
h[Array.new] # Output: "Why?"

h[Hash.new] = "It doesn't make sense"
h[Hash.new] # Output: "It doesn't make sense"

But an object works differently...
h[Object.new] = "LOL"
h[Object.new] # Output: "nil"

But this one works as expected:
o = Object.new

h[o] = "LMAO"
h[o] # Output: "LMAO"

Tried this:
o = Object.new           # Output: #<Object:0x2c78c10>
h["#<Object:0x2c78c10>"] # Output: nil

Tried it in Python and PHP and it throws an error.
I'm just curious about how it works, and why would you want to use an array, or a hash as hash key in ruby?
Thanks.

A:

Why wouldn't you? You can use any object as a Hash key in Ruby. (Well, any object that responds to hash and eql?, but since there are definitions of those in Object, that's pretty much all objects.) It would be strange and inconsistent if you would arbitrarily exclude two classes from that.

A:

[How what you tried works first, why such keys are useful at the end.]
Behavior depends on the key, specifically what its .hash and .eql? methods do.
The 2 common strategies (appearing in many languages) are hashing by value and by identity.  [Well, there is a third one: not allowing certain types as keys.]
An extremely familiar example is strings hashing by value:
pry(main)> a = 'text'
=> "text"
pry(main)> b = 'text'
=> "text"
pry(main)> a.equal? b  # Identity: not same object
=> false
pry(main)> a.eql? b    # But match for hash purposes
=> true
pry(main)> h = {a => 'foo'}
=> {"text"=>"foo"}
pry(main)> h[a]
=> "foo"
pry(main)> h[b]
=> "foo"

Right?  It'd be very surprising and inconvenient if h['text'] wouldn't work simply because the 'text' you passed is not the very same 'text' object you inserted.
The same tends to be true of other types that "compare by value".
When you look up h[['Sherlock', 'Holmes']], you probably care about the array's content, not its distinction from other arrays with same elements.
E.g. in Ruby, arrays, hashes, sets define .hash and .eql? to hash by value.
[See below for the "why?" part.]
Now what about custom objects?  Well, you decide :-)
But the default behavior of Object, in Ruby as well as Python, Java and probably others is hashing by identity:
pry(main)> a = Object.new
=> #<Object:0x0055a2f2e803d8> 
pry(main)> b = Object.new
=> #<Object:0x0055a2f2387288>
pry(main)> h = {a => 'a', b => 'b'}
=> {#<Object:0x0055a2f2e803d8>=>"a", #<Object:0x0055a2f2387288>=>"b"}
pry(main)> h[a]
=> "a"
pry(main)> c = Object.new
=> #<Object:0x0055a2f214e520>
pry(main)> h[c]
=> nil

Same holds for subclasses of Object, unless you override the relevant methods (eql? and hash in Ruby).  Why?

The concepts of "value" and "equality" vary, and can only be user-defined.
There may not be a concept of "value" at all!
Let's say I have GUI Window objects, and want a hash mapping windows to their position.  Are 2 different windows ever "equal"?  No.  All I can go by is "this window is here".
Many custom objects are mutable, so identity matters [see below].

All this adds up to identity being the only default hashing behavior that Object can provide with no explicit input on your part.  (The alternative would be not allowing keys that didn't explicitly define how they hash at all.)

Notes on Mutability:

Thinking in terms of "values" is easiest with immutable types — their identity never matters.
Mutating an object you've used as a hash key is bad.  It now sits in the wrong bucket, making the hash table internally inconsistent => at best, you'll not be able to lookup this entry, neither by original key nor by current value (I think Ruby sits here), at worst you could trigger implementation bugs and crash...

Both lead to a strong correlation: it's very common for immutable types to hash by value, and mutable by identity (that's safe, if hash function is only derived from identity, mutations don't matter).
So, how do you hash by an array value?  Varies across languages...

In Ruby, you should ask even about strings, as they're mutable!
Ruby played both sides here.  You can mutate strings unless you .freeze them.  (And there is a separate type — symbols — that are somewhat string-like but immutable, and even ensure there is at most one instance with a given value.)
But mostly you do treat strings as values, and as we said you really want them to hash by value too.  So they do :-).
Plus Ruby special-cases strings to be cloned and frozen when used as keys, to keep you from trouble.
Ruby arrays, hashes, sets hash by value too; it's your responsibility not to mutate them.
Python takes the stricter approach that you can't use mutable builtin types as keys.  They don't (and can't) really enforce it for custom classes, but eg. Python arrays (list) override .__hash__() to raise an exception.  Instead, Python provides a sister immutable tuple type, that hashes by value.  Similarly, you can't have set keys but python provides frozenset...
Java collections took a deliberate decision to not have a combinatorial explosion of interfaces.  All lists, mutable or not, hash by value.  And you can use e.g. mutable ArrayList as hash keys.  And it's your responsibility to not mutate it.

So back to Why would you use non-string keys, say arrays?
Frequently your data is naturally organized by several "keys".
You can use h[[x, y, z]] as a "multi-dimesional" hash.
You could also use nested "one-dimensional" hashes: {x => {y => {z => value}}}; sometimes one is more convenient, sometimes the other.
Consider graph edges.
Let's say Magellan's expedition left us these 2 maps, to navigate the world's oceans:
a = {['Atlantic', 'Pacific'] => 'Straights of Magellan',
     ['Pacific', 'Indian'] => 'Indonesia',
     ['Indian', 'Atlantic'] => 'Cape of Good Hope'}
n = {'Atlantic' => {'Pacific' => 'Straights of Magellan'},
     'Pacific' => {'Indian' => 'Indonesia'},
     'Indian' => {'Atlantic' => 'Cape of Good Hope'}}

If you are in ocean1 and want to get to ocean2, you look up a[[ocean1, ocean2]], or n[ocean1][ocean2].
If you want to see all connections at once, you do a.keys.  Nice!
With the nested representation it's trickier.
But if you're in ocean1 and are asking "where could I go next?", the flat a representation is tricky, here n[ocean1] wins.

But now consider navigating east.  Our graph is "undirected"; a passage from Atlantic to Pacific also works the other way around.

The n[from][to] access pattern is inherently "directed".  To model it working both ways, we need 2 entries for every passage...
The 1-level a hash may be cleaner then.  All we need is either normalizing order on every access (sorting the 2-element arrays), or keys that don't care about order:
pry(main)> w = {Set.new(['Atlantic', 'Pacific']) => 'Straights of Magellan',
pry(main)*      Set.new(['Pacific', 'Indian']) => 'Indonesia',  
pry(main)*      Set.new(['Indian', 'Atlantic']) => 'Cape of Good Hope'}  
=> {#<Set: {"Atlantic", "Pacific"}>=>"Straights of Magellan",
 #<Set: {"Pacific", "Indian"}>=>"Indonesia",
 #<Set: {"Indian", "Atlantic"}>=>"Cape of Good Hope"}

pry(main)> w[Set.new(['Atlantic', 'Pacific'])]
=> "Straights of Magellan"
pry(main)> w[Set.new(['Pacific', 'Atlantic'])]
=> "Straights of Magellan"

See?  Fancy types as keys are fun!
A less abstract example: Fitting an existing API.  If I have words and decide to segment them by length, I do:
pry(main)> ['foo', 'bar', 'baz', 'quux'].group_by { |word| word.length }
=> {3=>["foo", "bar", "baz"], 
    4=>["quux"]}

Now let's say I want to group words that have same length and same first letter.
pry(main)> ['foo', 'bar', 'baz', 'quux'].group_by { |word| [word[0], word.length] }
=> {["f", 3]=>["foo"], 
    ["b", 3]=>["bar", "baz"], 
    ["q", 4]=>["quux"]}

Easy and natural!  I used existing logic, just with more complicated keys!
How could you write it in a language that wouldn't let you compose keys this way?

