Parentheses and Curly Braces in Ruby, Who Needs em’?

Anyone who has ever written or read code in Ruby knows that looking over the file it is not at all uncommon to never see a single use of () on any function or method call. Additionally, the use of {} can be almost exclusively seen when assigning a variable to a hash literal.

hash1 = {:a => 1, :b => 2}

Why is that the case though? If parentheses and curly braces aren’t used how will we know what our code is supposed to do? Where are the delineations between what is a variable and what is a method? How do we create blocks of code?

To answer the first question we need to think about why Ruby was made. According to the creator, Yukihiro Matsumoto ( affectionately known as “matz” ), “As a language maniac and OO fan for 15 years, I really wanted a genuine object-oriented, easy-to-use scripting language.” With this as a founding principle Ruby was meant to be a pleasure to read. As if instead of reading a complicated list of technical instructions for a computer to execute you are reading a good book, with a logical story and explanation from start to finish. Because of this many of they necessary punctuation in other programming languages like parentheses and curly braces are “optional” or not used at all in relation to their traditional uses like invoking functions and separating code blocks.

Now if those normal punctuations are now optional in Ruby how does the language know what to do? The first thing Ruby does is it checks to see if a variable has been assigned to a value.

c = 1

The left side is our variable and our right side is our value. Now let’s say we have something like this.

puts c

This will output “1” to the terminal (and return nil). Ruby sees the word “puts” and saw no assignment to a variable “puts” anywhere. So the language will think ok, this might be a function, and in Ruby’s documentation we see “puts” is a function which will take in an argument, and output the value to the terminal. In this case we have a variable “c”. Ruby looks up within the file and goes, cool, “c” is assigned to the number “1” that’s what “c” must be and “puts” will do its thing to output 1 to the terminal. No parentheses necessary.

Now let’s consider this case.

def c 
puts 1
end
c

Same output as above. This time what we have done is we have made “c” a reference to a function by using the keyword (words within Ruby that have special meaning and can be used to perform various tasks, full list here)“def”. Now as Ruby is reading this example it hits the keyword and knows that immediately after the keyword is going to be something that will reference a block of code (wait I don’t see any curly braces…we’ll get there too). Inside the block is a familiar function “puts” and a new keyword “end”, which denotes the end of a block of code. When “c” is now referenced after “end” Ruby looks from the top of the file and sees no assignment to a variable “c” and thinks ok, this might be a method, and it is. So Ruby reads within the method definition and calls the function “puts” to output the value of 1.

Now let’s look at another example. (from Ruby Documentation)

def d
print "Function 'd' called\n"
99
end
for i in 1..2 do
if i == 2
print "d=", d, "\n"
else
d = 1
print "d=", d, "\n"
end
end

What do you expect to see as the output? (“print” does something similar to “puts” however it leaves out all newline characters so that is why we add the \n to the strings above)…In this case we get some interesting behavior from the Ruby parser.

d=1
Function 'd' called
d=99

What happened here? We’ve got a few things going on with a couple of new keywords, “for”, “in”, “do”, “if”, and “else”, there’s a funky “i” in there too. The first thing we did was define a new method “d”. Then we started a “for…in” loop and set a counter “i”, assigned to a value starting at 1 and incrementing up to 2 (1..2) and opened a control structure with “do”. Within that control structure we have two other conditional structures started with “if” and “else”. Like you would expect, “if” a certain condition, in this case “i == 2” (double “=” signs denote equality checks because a single “=” is the assignment operator.), is true we will then execute the code within the control structure, “else” we execute the code in the else control structure and we finish the statement with the “end” keyword. Last, we finish the “for…in” structure with another “end”. This control structure will execute as many times as the incrementor “i” takes to reach the final value, in this case, 2.

Within the for…in structure the first time we run through we hit the else structure because i does not equal 2. Then we are assigning “d” to a value of “1”, wait we already told Ruby that “d” was a method? Ruby assumes that now you want the reference to “d” to be a value of “1” so it changes it. Ok now when we print d it will be the value of 1. So then the next time we run through the for…in control structure i is equal to 2 and the if condition is true so we execute the code within the if statement. What happens when we print d this time? We get a call to the function and we see “Function ‘d’ called” and “99” printed to the screen. Huh? didn’t we just change d to the value of 1? Yes we did, and at that point d is currently assigned to 1. However the first time Ruby read the file it did so logically. Left to right, top to bottom. The first time it saw “d” it was being made into a reference to a function. The next time it saw “d” is within the “if” statement. Even though the code isn’t executed Ruby still reads it. That made “d” a reference to the function named “d”. As it is going down the file it then reads within the else statement and it sees that “d” is now reassigned to the value of 1. So the next time Ruby sees “d” it is now the value of 1. Those were just the steps Ruby took to read the document. Then the document needs to be executed. When it is executed the control structures handle the execution and we get the output above printed to the screen. Wow, that was a bunch of confusing steps, and at times arguably illogical. Ruby agrees, however it will not throw an error because “d” is referenced somewhere before the first time it is read.

How does this relate to parentheses and curly braces? Let’s take a look at similar code in JavaScript.

function d(){
console.log("Function 'd' called")
return 99
}
for (let i = 1; i <= 2; i++) {
if(i === 2){
console.log("d=", d)
}
else{
d = 1
console.log("d=", d)
}
}

Here we have a function definition “d()” and nowhere else through the code is the function “d()” ever invoked. So “d” is only ever known as its reference to “1” from the else block and we get.

d = 1
d = 1

printed to the console.

Because of how JavaScript reads and parses the file the below JavaScript code will also work.

for (let i = 1; i <= 2; i++) {
if(i === 2){
console.log("d=", d)
}
else{
d = 1
console.log("d=", d)
}
}
output:
d = 1
d = 1

Let’s see when we try something similar in Ruby.

for i in 1..2 do
if i == 2
print "d=", d, "\n"
else
d = 1
print "d=", d, "\n"
end
end
output:
d = 1
Traceback (most recent call last):
in `block': undefined local variable or method `d' for main:Object (NameError)

We get an output of “d=1” and an error? When Ruby was reading this file it was looking from the top and initially “d” isn’t set to anything. It remembers that “d” does not yet exist, however that hasn’t been executed yet. Then on execution the first “if” block is ignored and skipped over, then “d” is assigned to 1, however when we run through the for…in loop again, now “d” doesn’t exist in Ruby’s memory and we get a “NameError”.

This makes sense logically, Ruby is telling us, hey, this may be a logical order to write code in another language where you assign the variable after you have written it the first time as long as you have the proper conditional statement but that is really confusing (it gets into some very technical aspects of languages that are outside the scope of this article). When we write code in Ruby we want our story to follow a proper beginning, middle, and end. By making us write in this logical way Ruby is able to leave out some of the traditional punctuation while still performing object oriented programming tasks.