DEV Community

Discussion on: Ruby 3.1 – Shorthand Hash Syntax – First Impressions

Collapse
tantle profile image
tantle

I've been using Ruby for 15 years and it is by far my favorite programming language! As I introduce friends and colleagues to Ruby, the two things that seem to constantly trip them up are the symbol syntax vis-a-vis keyword arguments, and hash keys being either symbols or strings.

In my view, it's deeply unfortunate that as of Ruby version 1.9 there are two different ways of declaring symbols that can be quite confusing to people just coming to the language. This is compounded by the fact that keyword arguments share the same syntax, but aren't really symbols per se.

For example, the new hash syntax seems innocuous and both of these examples are equivalent:

hash = { :foo => 'bar', :bar => :bar }      # => {:foo=>"bar", :bar=>:bar }
other = { foo: 'bar', bar: :bar }           # => {:foo=>"bar", :bar=>:bar }
Enter fullscreen mode Exit fullscreen mode

However, when looking at the result, there is no way to determine which syntax was used when the hash was declared. This impacts providing guidance in error messages, because we may use the new syntax in the message, when the user wrote the code using the old syntax.

The new hash literal declaration syntax can only be used when keys are symbols. For example, would we expect the bug in the code below obvious to language newcomers?

obj = Object.new                                        # => <Object:0x00007f8c4f9d1ad8>
hash = { :foo => 'bar', :bar => :bar, 'obj' => obj }    # => {:foo=>"bar", :bar=>:bar, "obj"=>#<Object:0x00007f8c4f9d1ad8>}
other = { foo: 'bar', bar: :bar, 'obj': obj }           # => {:foo=>"bar", :bar=>:bar, :obj=>#<Object:0x00007f8c4f9d1ad8>}
Enter fullscreen mode Exit fullscreen mode

The ability to coerce a string into a symbol already exists in Ruby, but it uses the original "legacy syntax", which I believe makes the intention much more clear.

string = 'hello'    # => "hello"
symbol = :'hello'   # => :hello
Enter fullscreen mode Exit fullscreen mode

Furthermore, both the old and new syntax may be freely combined when declaring hash literals which can lead to some odd looking declarations. For example in order to fix the bug in earlier example, should we write:

other = { foo: 'bar', bar: :bar, 'obj' => obj }         # => {:foo=>"bar", :bar=>:bar, 'obj'=>#<Object:0x00007f8c4f9d1ad8>}
Enter fullscreen mode Exit fullscreen mode

When evaluating the validity of the expression above, one must bring several different syntaxes to mind.

The alternative syntax may only be used safely when all hash keys are symbols, and in all other cases the original syntax must be used.

The new syntax effectively doubles the search space when scanning code for references to a given hash key that is a symbol because it might be declared in one of two different forms (leading colon or trailing colon).

The ActiveSupport::HashWithIndifferentAccess class exists in part to deal with some of the shortcomings of dealing with Ruby hashes. I really wish that we didn't need such a class and that Ruby would address these issues in a fundamental manner that would help make the language more approachable to newcomers.

Thread Thread
baweaver profile image
Brandon Weaver Author

I mean I agree with you on the general premise that Symbol and String intermingling is confusing, but there's zero chance that gets changed due to the way Ruby works, and I've resigned to that. I've also had that argument several times, but originally in Ruby Symbols and Strings were much more different in terms of GC, memory, and identity. Because of that past it's impossible to change.

As far as this being the straw that broke the camels back? I would disagree. Keyword arguments have done this for a long time, the only difference now is that they can be used on "write" (creating a hash or calling a kwarg function) rather than "read" (kwarg function argument definition).

Thread Thread
vgoff profile image
Victor Goff

Even further than that, it is not only symbol and string as keys, but anything that responds to hash can be used as a key.