DEV Community

Ruby 3.1 – Shorthand Hash Syntax – First Impressions

Brandon Weaver on September 12, 2021

It's the time of year again, and with it comes a bundle of new Ruby 3.1 features getting approved and merged ahead of the December release. This s...
Collapse
 
tantle profile image
tantle

So, what is the symbol syntax in Ruby 3.1 now? Do they start with a colon, or do they end with a colon?

Collapse
 
baweaver profile image
Brandon Weaver

Same as it has been. This plays off the json style of Hash keys.

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

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.

Collapse
 
omrisama profile image
Omri Gabay

That is SUCH a tiny diff for a big change, wow

Collapse
 
baweaver profile image
Brandon Weaver

I may add more tests for edge cases, I can see some potential for issues later without a more formal spec.

Collapse
 
aivils profile image
Aivils Štoss

This creates copies of the object. The pointer is lost and at the smallest change the code must be changed 101 times. At least it's under javascript. I do not see any progress.

Collapse
 
baweaver profile image
Brandon Weaver

What are you referring to?

Collapse
 
aivils profile image
Aivils Štoss

Old time javascript "foo = {}; foo.bar = 123;" . When passing foo as an argument to a function, it was passed as a pointer. In the old days javascript to create an object copy was cumbersome "foo_copy = JSON.stringify (foo);". By modifying the code accordingly, it was quite safe to rely on adding the foo attribute, which will be available in the called functions. Modern javascript makes it very easy to create a copy of an object "const {not_my_variable1, not_my_variable2} = foo;". But when I changed the code I wrote "foo.bar = 123;" and in modern javascript I have to check every call to a function to see if a copy of the object has been created and if there I also have to type an extra "bar" variable so that it can be passed to the next function.
This is an unwanted side effect for the syntax sugar.

Collapse
 
mortengrum profile image
Morten Grum

Cool. Including methods will open for a new (and simpler) way to create polymorphism.

Collapse
 
baweaver profile image
Brandon Weaver

Oh? Have a few examples of ideas there?