When I first began to learn Ruby at Flatiron, I brought my previous experience with JavaScript along for the journey in hopes of narrowing the gap between the two syntaxes. While understanding some key concepts of programming fundamentals was helpful, I soon found myself in the far reaches of a very alien landscape of implied abstraction that seemed to counter everything I had previously known.
Now that I have trekked these past 6 weeks from the southern shores of simple do
and def
, all the way to the northern highlands of Rails 5, I find myself ironically jarred by setting foot in the familiar territory of JavaScript again.
Herein I will endeavor to describe the fundamental differences between JavaScript and Ruby syntax from a Rubyist's perspective and offer my strategies for achieving better mental dexterity when alternating between these two languages.
Explicit Implications
At the ground level, the major disconnect between Ruby and JavaScript is that Ruby is considered to be "strongly typed," meaning data types relate to one another in very specific ways; while JavaScript is considered to be "weakly typed," meaning data types are much more mutable and can relate to one another in less specific ways.
Where Ruby allows for implied logic with explicit rules for its data types, JavaScript requires explicit logic with more implied rules for its data types.
A perfect example of the difference in the rule sets is how Ruby treats integers and floating point numbers as separate data types that must be converted to act on each other, while JavaScript simply has numbers that can be either integers or floating values.
This clearly means there is a lot more freedom in a programmer's range of motion when working with JavaScript, however, that same range of freedom can yield unexpected results when embracing it from the background of a Rubyist.
A perfect example of the divide in how logic is treated differently is the fact that unless a value is explicitly returned using the return
bare word in JavaScript, functions have an undefined return value.
This flies in the face of Ruby conventions that favor implied returns at the final line of a method. But if one thing has become clear so far on my journey of learning to code, it is that repetition breeds knowledge and knowledge breeds skill. In other words, doing things in a standardized way helps cement concepts. To that end, I have found that using either a commented return
above my last line or actually applying the return
bare word to my last line in Ruby helps me stay grounded across both languages.
Do or Do Not, There Is No Pry
There are do
loops in JavaScript that emulate behavior that is similar to the do
blocks found in Ruby, but they have a very important difference.
- The
do
blocks in Ruby do not trigger if either their condition isn't met or the iterator they are passed to is unable to fire at least once. - In JavaScript,
do
loops evaluate their condition only after executing their code.
This means that do
loops always trigger at least once in JavaScript, as illustrated below.
The differences in how do
behaves between these two languages presents the first challenge when navigating JavaScript syntax as a Rubyist. The muscle memory we develop in Ruby can insert itself all too easily when we're writing in JavaScript without us even catching it when it happens. Luckily, there is a strategy we can use in Ruby to help minimize this growing pain.
Curly Boys to the Rescue
Some situations will still require you to employ the do end
syntax, such as embedding logic into .erb
files. But for the most part, the best way I have found to develop cross-language muscle memory is to pass blocks in Ruby using curly braces whenever possible.
The added advantage to adopting this syntax is it leaves the possibility open for method chaining for those occasions where the rule of adhering to functional readability can be flexed to write logic as a single line.
The case for using curly braces over do end
is strengthened by adding shorthand key: value
hash notation into the mix. When used in combination, these conventions of syntax apply to not only JSON but also CSS, creating a much more unified vocabulary of muscle memory overall.
Perhaps the biggest difference when adjusting to JavaScript that ranks very near the least popular among Rubyists is the lack of native support for a utility resembling binding.pry
or byebug
.
While there are solutions for this as mentioned in this article by Kevin Kim that are functionally synonymous with pry
in the context of JavaScript, problems that occur when developing front-end code tend to show up as nasty surprises in the browser the majority of the time.
These situations do not necessarily imply that a given project will be created on NodeJS or that a given bug will crop up when testing in Chrome. This means that despite having utilities available under NodeJS or Chrome's developer tools, there will be times where these may not apply.
Enter our hero: console.log()
It's Better Than Bad, It's Good
The simplest explanation of how console.log()
works is that it is a utility function that prints a copy of its string argument to the console window of the browser. By interpolating variables into a string using template literals, an approximation of the same behavior as pry
can be achieved; albeit not as user friendly.
It is important to note that depending on the scope of the function where console.log()
is called, it may be impossible to render the output of the function in the console.
Because of this, it is best to be aware of how to use console.log()
, but factor in the availability of debugging tools when deciding how to develop the front-end code of a project.
Template Literals
Thankfully, the learning curve for interpolating variables into strings is a lot more straight forward. The syntax is nearly identical, as shown here.
Fun fact: the type of the "back tick" character is something called a "diacritic" and the name of the back tick character itself is a "grave". As a way of remembering what type of string quotation allows for interpolation in JavaScript, you can use the mental association:
"Interpolating variables is 'gravely' important"
Inverted Instantiation
One of the most difficult aspects of transitioning to JavaScript from Ruby is the muscle memory involved in instantiating a class. While we have always written a call to Class.new
in Ruby, now we have to come to terms with new Class()
. It may seem like a small difference, but it can have quite an impact on the flow of a project.
Because the majority of the class instantiation I write in Ruby now occurs inside of Rails controllers, a strategy that I have found for standardizing between these two syntaxes is to completely abstract my #new
action into a before_action
helper method. This simultaneously declutters my code and allows me to mentally conceptualize these two situations as unique, evading crosstalk.
Exhibitionism in ES6
Now that we are taking how classes are instantiated into consideration, let's step back a moment and review how helper methods like before_action
are defined. According to Rails convention, these methods are defined below the private
bare word inside our controllers. What this means is that the helper methods are private to that controller instance when it is created during a server request. Things work very differently in JavaScript.
As of this writing, all properties and methods of all class instances are effectively (and natively) public in JavaScript.
Returning to the point that JavaScript implies how data types interact, this implied system of relationships also extends to function scope. By exploiting aspects of how function scope works in JavaScript, it is possible to approximate the same behavior as the private
bare word in Rails, but with a lot more work and high level understanding involved.
Currently, the intricacies of available means of privatizing class properties and methods extends beyond the scope of my skillset and the topic is a very deep rabbit hole that exceeds the reach of this article. More information about this process can be found here by Chris Ferdinandi.
That said, it is important to understand that unlike working in Ruby, acting upon data in JavaScript does not always act upon data that already exists, but rather can create data through the expectation that data exists.
One prime example is assigning a value to an object property that does not exist, thereby creating and appending it to the object in the process.
This article is being published while the most recent standard of JavaScript is still ES6. However, the next iteration of the standard — currently referred to as ESNext — is anticipated for release some time in 2019 or soon after. With it will come native support for private class properties and methods that will remove the necessity of using closures to conceal class variables.
Let it Let
Unfortunately there is one elephant of syntax difference in the room that is large enough no amount of workflow adjustments in either language can hide it. This elephant is the diverse ways in which the structure of statements and the use of bare words differ between these two languages.
The purpose of this article is to provide tactics for reducing the impact of higher level differences when writing in JavaScript from a Rubyist's perspective.
Due to the breadth of the information regarding exact syntactical differences you will encounter, I recommend reading this excellent article by Edozié Izegbu to better prepare for these inevitable sticking points.
Conclusion
While my journey of learning to code is only just beginning, I hope that these strategies can help to guide or inspire you to begin standardizing how you approach writing in multiple languages.
Top comments (2)
It's so rare to see someone using
[do while]
in javascript.See imba, it's a language with ruby syntax but you can use it like javascript. Hopefully, you will like it. Oh, and it's fast :)