Getting a key from a Hash

Hi everyone,

I have a question regarding the Hash type.

If I have a myhash = {"one" => 1, "two" => [1, 2, 3]} of type Hash(String, Array(Int32) | Int32).
Why is it that onekey = myhash["one"] will be of type (Array(Int32) | Int32) and not Int32 ? Please assist, I’m clearly missing something and I couldn’t find a clear explanation on the documents.

Its because you have defined Hash value type as a union of types. So what you get back is that union. If you need to find what value type you have retrieved, you should be performing a test via is_a? or case statement.

myhash = {"one" => 1, "two" => [1, 2, 3]}
v = myhash["one"] 
p v.is_a?(Int32)  # => true
p v.is_a?(Array) # => false

# OR

p case v
when Int32 then "got int"
when Array then "got Array"
end   # => got int

Thanks for the response! Is it then possible to have v as Int32 and not (Array(Int32) | Int32) and work from there?

Say I want to iterate over the "two" key, the following wouldn’t work since v would still be of type (Array(Int32) | Int32):

myhash = {"one" => 1, "two" => [1, 2, 3]}
v = myhash["two"]
v.each do |i|
   puts i
end

This confused me too when i first looked at crystal.

the thing that helped me was to put myself in the compilers’ shoes.

As you noted , this won’t work, because the compiler cannot be sure what type the variable “v” contains. It only knows that it must be either a Int32 or a Array(Int32) but not which.

that is why in crystal the good old “if then end” has a (what i call) secret second meaning.
If you do something like:

if v.is_a?(Int32)
  puts "on this branch 'v' is guaranteed to be a Int32, and the compiler knows this"
end

once you are in a branch “guarded” by such a conditional, the compiler knows the exact type of the variable, and you can treat it as such, although outside of the branch it can be a union.

(Please someone correct me if anything here is wrong)

2 Likes

When working with union types, you should be performing a type check as stated above to understand what type you are dealing with. But if you are sure enough about the type, then you can use type reflection pseudo methods like as , as? etc to restrict the type.

Remember: Crystal don’t have type casting :wink:

so simply you can have

if arr = myhash["two"].as?(Array(Int32))
  arr.each do |i|
    puts i
  end
end

OR

If you are bold enough to deal with Runtime exceptions then you can go with as like

myhash["two"].as(Array(Int32)).each { |x| p x }
2 Likes

Thank you all for the answers! Things are clear now.is_a? and .as are what I was looking for. Thanks!