Seven Languages In Seven Weeks: Ruby - Day 1

As I blogged about earlier, I've just started the new Pragmatic Programmers' book, Seven Languages in Seven Weeks. Each of the languages comes with homework assignments which I shall be discussing on the blog. I just finished Ruby - Day 1.

HW1: Print the string "Hello, world."

# Print the string "Hello World." To start off, we're going to

# output the double-quoted value. This will evaluate the string

# for any embedded variables.

puts "Hello World"

# Demonstrate the embedded variable behavior.

message = 'Hello World'

puts "#{message}"

# Print the string 'Hello World.' When using single quotes, the

# string will be treated as a literal; that is, no evaluation of

# the string will take place.

puts 'Hello World'

# As a final approach, we're going to remove some of the "syntactic

# sugar" and fall back on a more tradional look and feel.

puts( "Hello World" );

# There is also a "print" method will will print to the standard

# output (like puts), but will not append any new line.

print "Hello World\n"

When dealing with strings in Ruby, those surrounded with double-quotes are evaluated for variable replacement; those surrounded with single-quotes are treated as literals. Regardless of these rules, you'll see me using double-quotes most of the time. I know it entails more overhead but, until I am more comfortable with the syntax, I'm going to do what I can to make the code look more familiar.

When we run the above code, we get the following console output:

Hello WorldHello WorldHello WorldHello WorldHello World

HW2: For the string "Hello, Ruby," find the index of the word "Ruby."

# For the string "Hello, Ruby" find the index of the word, "Ruby."

# First, let's set up a value to test with.

phrase = "Hello, Ruby"

# We can use the index method to locate the first index of a given

# value within the string.

puts( phrase.index( "Ruby" ) );

# The index() method also accepts a regular expression pattern which

# can be defined with Javascript-like RegExp literals.

puts( phrase.index( /Ruby/ ) );

# If we're going to use regular expressions, we can use the regular

# expression operator to test for the position of the first pattern

# match within a given value.

puts( "Hello, Ruby" =~ /Ruby/i );

I love the fact that Ruby has a built-in Regular Expression operator (=~). This operator returns the index of the first match of the given pattern with the given string. It's like ColdFusion's reFind() method (except for that Ruby doesn't treat zero as a falsey - only false and nil).

When we run the above code, we get the following console output:

777

HW3: Print your name ten times.

# Print your name 10 times.

# We can start off with a basic while loop.

i = 1

while (i <= 10)

puts( "Ben" );

i = (i + 1);

end

# Ruby uses a lot of Ranges. These are sets of values that go from

# a start value to an end value. We can use a FOR loop to loop over

# the ranges.

for i in (1..10)

puts( "Benjamin" );

end

# We can also use ranges with iteration.

(1..10).each{

puts( "B-Jamin" )

}

# I am not sure if this creates a range, or is doing something else

# more "special"; but, we can also use the Integer's upto() method

# to iterate from one number to another.

#

# In this version, our iteration body is using do/end. This would

# also work with the {..} approach; I'm just trying to get used to

# the enormous number of ways in which you can execute code.

1.upto( 10 ) do |i|

puts( "Big Ben" );

end

# We can also use the times method to iteration from zero to n-1

# where n is our base value (NOTE: This one is a Married With

# Children reference).

10.times{

puts( "Grand Master B" );

}

This is really the first block of code that gave me a goofy smile. Ruby definitely has some sweet collection-based functionality. Ranges and functions like .upto() and .times() just make looping a joy. Although, I have to say that I could not find a straight-forward for() loop that matched the more traditional style (ie. for(init,condition,step)). It seemed that this kind of basic for() looping had to be finagled with a while() loop or something to that effect.

HW4: Print the string "This is sentence number 1," where the number 1 changes from 1 to 10.

# Print the string "This is sentence number 1", where the number 1

# changes from 1 to 10.

# Since we've already done a bunch of iteration, I'm doing to bring

# back the Range-based iteration and just use string substitution.

(1..10).each{ |i|

puts( "This is sentence number #{i}" );

};

# We can also create implicit arrays to act as collections. And, we

# can then iterate over them with the each method.

[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].each{ |i|

puts( "This is sentence number #{i}" );

};

# We can also take ranges, convert them to arrays, and then iterate

# over them in the same way as we did with the above array.

(1..10).to_a().each{ |i|

puts( "This is sentence number #{i}" );

};

# Ruby seems to celebrate putting stuff on one line, which I happen

# to think makes for horrendous readability. But, I figured I'd play

# around with it a bit. One thing you start to see is this kind of

# post-execution condition.

i = 0

puts( "This is sentence number #{i+=1}" ) while (i < 10)

# Here's the same kind of loop, except it's using the "until" clause

# rather than the while clause.

i = 0

puts( "This is sentence number #{i+=1}" ) until (i == 10)

Here, I was really just trying to explore some more looping paradigms. As you can see, Ruby has a ton of ways to do looping, including a number of single-line approaches. I don't much care for looping on a single line, though it appears to be a popular mechanism in the language.

When we run the above code, we get the following console output:

This is sentence number 1This is sentence number 2This is sentence number 3This is sentence number 4This is sentence number 5This is sentence number 6This is sentence number 7This is sentence number 8This is sentence number 9This is sentence number 10This is sentence number 1This is sentence number 2This is sentence number 3This is sentence number 4This is sentence number 5This is sentence number 6This is sentence number 7This is sentence number 8This is sentence number 9This is sentence number 10This is sentence number 1This is sentence number 2This is sentence number 3This is sentence number 4This is sentence number 5This is sentence number 6This is sentence number 7This is sentence number 8This is sentence number 9This is sentence number 10This is sentence number 1This is sentence number 2This is sentence number 3This is sentence number 4This is sentence number 5This is sentence number 6This is sentence number 7This is sentence number 8This is sentence number 9This is sentence number 10This is sentence number 1This is sentence number 2This is sentence number 3This is sentence number 4This is sentence number 5This is sentence number 6This is sentence number 7This is sentence number 8This is sentence number 9This is sentence number 10

HW5: Run a Ruby program from a file.

This is actually how I was running all of these homework (HW) assignments. As long as "ruby" is in a folder in your system paths, you can use it to run an .rb file from the command line / console:

ben$> ruby hw1.rb

HW6: Bonus problem: If you're feeling the need for a little more, write a program that picks a random number. Let a player guess the number, telling the player whether the guess is too lower or too high.

# Bonus problem: Pick a random number and have the user guess

# the number. When the guess, if incorrect, tell them if they

# are too high or too low.

# Pick a random number between 1 and 10.

randomNumber = (rand( 10 ) + 1);

# Keep looping, getting a value from the user, untli they have

# selected the random number.

begin

# Print a message for the user:

puts( "Guess the number between 1 and 10" );

print( "Guess: " );

# Get the number from the user. This number will come back as a

# string. We can then convert it to an integer so that we can

# compare it to our random number. If the user did not enter a

# numeric string, to_i() returns zero (0).

userGuess = gets().to_i();

# Check to see where the number falls compared to the randomly

# selected number.

if (userGuess < randomNumber)

puts( "...Guess higher!" );

elsif (userGuess > randomNumber)

puts( "...Guess lower!" );

end

end while( userGuess != randomNumber )

# At this point, ther user had guess the random number

# which has caused the begin/end loop to end.

puts( "Heck yeah! You guessed #{randomNumber} correctly!" );

The one thing I really didn't like about this problem was coming across the "elsif" clause of an IF/ELSE statement. I mean really? Would it have killed you guys to include the "e" in elsEif? I know Ruby is geared towards making the programmer productive; but, should that ever be done at the expense of single-letter readability?

When we run the above code, we get the following console output:

Guess the number between 1 and 10Guess: 1...Guess higher!Guess the number between 1 and 10Guess: 2...Guess higher!Guess the number between 1 and 10Guess: 3...Guess higher!Guess the number between 1 and 10Guess: 4...Guess higher!Guess the number between 1 and 10Guess: 5...Guess higher!Guess the number between 1 and 10Guess: 6...Guess higher!Guess the number between 1 and 10Guess: 7Heck yeah! You guessed 7 correctly!

As I was doing research for this answer, I also came across the fact that you could use Range objects within switch statements (case statements). I know this is not really practical in our particular problem domain; however, I wanted to take this as an opportunity to play with using Ranges for our case conditions. I have reworked the above code to use a CASE block rather than an IF block.

# Bonus problem: Pick a random number and have the user guess

# the number. When the guess, if incorrect, tell them if they

# are too high or too low.

# Pick a random number between 1 and 10.

randomNumber = (rand( 10 ) + 1);

# Keep looping, getting a value from the user, untli they have

# selected the random number.

begin

# Print a message for the user:

puts( "Guess the number between 1 and 10" );

print( "Guess: " );

# Get the number from the user. This number will come back as a

# string. We can then convert it to an integer so that we can

# compare it to our random number. If the user did not enter a

# numeric string, to_i() returns zero (0).

userGuess = gets().to_i();

# This time, we are going to be using the CASE statement to

# find the position of the guess within the 1..10 range. We are

# going to construct a lower and upper range. The lower range

# uses ... which is exclusive. The upper range uses .. which is

# inclusive.

lowerRange = (1...randomNumber);

upperRange = ((randomNumber + 1)..10);

# Check to see where the number falls compared to the randomly

# selected number.

case( userGuess )

when lowerRange

puts( "...Guess higher!" );

when upperRange

puts( "...Guess lower!" );

end

end while( userGuess != randomNumber )

# At this point, ther user had guess the random number

# which has caused the begin/end loop to end.

puts( "Heck yeah! You guessed #{randomNumber} correctly!" );

As you can see, we are now using the randomly selected number to act as the slipping point between our upper and lower Range objects. What's cool about ranges is that a case block uses a tripple equals to evaluate matches; and, with ranges, the tripple equals operator will check to see if the given value is in the range.

When we run the above code, we get the following console output:

Guess the number between 1 and 10Guess: 1...Guess higher!Guess the number between 1 and 10Guess: 2Heck yeah! You guessed 2 correctly!

There's definitely some features of Ruby that are awesome; but, there's also some things that make me unhappy. I suppose some of that is just getting used to the new syntax (or lack thereof). One thing that I don't care for is how much of the code is optional. I am sure that a seasoned Ruby developer could come in a cut most of my code in half; but at what cost? Once you get above Assembly language, is efficiency ever really more important than readability?

I think for single-step for-loops, the range approach is simply beautiful. My concern is that if you ever need to do some sort of "i+=2" step, it looks like you have to fall back on a WHILE loop, which just looks less attractive.

But, maybe I just couldn't find the for() loop that allowed for a step-wise expression.

Here's what I remember of the rules (I'm just an amateur Ruby user still): {...} or do...end can be used for blocks, which are really an argument to a method. So they can be used on methods (upto for instance). You can't use {...} on language constructs like "for" "if", etc.

When it comes to blocks, the {...} differs from do...end only in terms of operator precedence. I typically use {...} for one-liners and do...end for multi-line blocks, but there are other approaches.

Trying my best :) This book is actually very grueling. The author really challenges you to figure out the problems. I'm loving it!

@Drew,

It only spends 3 "days" talking about Ruby. But the whole point of the book is that learning a number of features of a number of languages will actually help you learn all of the languages better. As he says in the intro (I think), "To get better at math, try learning Latin." Whether or not that makes sense, the point is that perspective leads to better understanding.

That said, as far as a technical manual, if you just wanted to learn Ruby head to toe, you might want something that holds your hand a bit more??

But really, I'm not one to recommend a book on a language I *just* started learning :)

@Phil,

So far, I'm loving it. It's like being in school, but only taking the cool classes :)

As for the commenting, I'm sort of in a weird place. Since these aren't going to be languages I have done before, I have no preferred editor. As such, I'm just using my ColdFusion Builder which doesn't know how to highlight the Ruby files. I am a man who loves white space, but I'm add a lot of extra white space here simply to narrow my thoughts as I type.

If you questioning the amount of comments, I'm simply trying to drill the concepts into my head by trying to explain *every* single step I take!

I'll take a look. I'm working on a Mac at the office and a Windows 7 machine at home. Been using Builder sometimes at home, sometimes just NotePad++.

@Phil,

In general, I do put a lot of comments in my code. Helps me think about what I'm doing. Plus, when I come back to the code later, it helps me remember what I did without having to mentally parse the code. Plus, since comments are typically light-gray, they also act a nice buffer between my lines of code. It allows me to focus on every individual line of code without being distracted.

At first glance, it looks like Ruby got a number of things from Perl. The =~ search operator, for example, and the "single-line loop with postfix conditional" for another. That's one of my favorite things about Perl, actually, and I miss it in other languages:

doSomething() until(done);

is so much prettier and more elegant than

until(done) { doSomething(); }

I look forward to the rest of these posts. (Especially once you get to Erlang!)

Until() is cool, but the one that really warps my brain is the "unless" concept. Ruby has this thing where you can use "unless" instead of using a NOT operator... I think. It quite literally hurts for me to think about it :)

unless false

puts( "Do IF false" );

end

This construct just really confuses me. I find it to be completely unreadable. In the book, they said it was suppose to more easily map to "English"? But, it doesn't compute in my head.

Even in its other context, it seems so odd:

puts( "Do IF false" ) unless( false );

Am I the only one who finds this very counter-intuitive?

@Chris,

Thanks for the link. About to move on to "Io" in the book; I'll check this out when I come back to Ruby.

One of the purposes of Ruby is to read as closely to English as possible. Your goal, in writing a Ruby program, is to be able to read it as easily and as quickly as you read English. That's why, for examples, Ruby does not force you to use various keyboard symbols in the syntax, and that's why it lets you things in multiple ways, including as one-liners. It's always up to you to choose the particular style that is easiest to read for any given part of your program.

As an example, Ruby does not require parentheses to call a method because, in Ruby, calling a method should read like an English sentence - and English sentences don't use parentheses to denote the direct object. While you might use

print('Hello');

when writing in Computerese, English is written as

print 'Hello'

.

As another example, English permits us to qualify sentences with appended subordinate clauses such as if, unless, while, and until. Ruby permits the same -

print 'I am happy!' if self.happy?

- so that you have the freedom to write your code somewhat like you would write a sentence in English. Now, it would be very awkward to append a subordinate clause to a sequence of clauses, in both English and in Ruby - so we typically use this convenience, in both English and in Ruby, only in simple cases.

- The square-brackets notation for creating arrays is known as the array literal notation, rather than the implicit array notation. Nothing is implicit in square-brackets notation - rather, because we are using syntax to create an array rather than using methods, it is called the literal notation of creating an array. Likewise,

I think there's is definitely going to be an adjustment period going from a more structures language to a more lenient language. I love to use semi-colons and parenthesis and double-quotes. But, that doesn't necessarily mean that they are better - it probably just means that is what I am used to.

I can logically accept that it's nice to read something in English; but, when you're not used to it, I think you tend to read code with the computerese mentality.

Another example of me wanting to use semi-colons when it seems to be patently not what the language actually wanted.

Thanks for the terminology points; I definitely do play a little fast a loose with my terms. Please know that that is not out of laziness - it's simply that I don't necessarily understand the distinction. Take "interpolate" - I definitely use the term "evaluate" to just mean that something works a certain way at run time. I know, not the best definition :) I'm still working this all out, so in all sincerity, I really do appreciate the terminology help.

Sorry about the inline code formatting. I had just assumed that this would only be used with "blocks" of code. Any thoughts on how to delimit inline and block level code? What about something like an "icode" tag for an inline code tag?

As a final thought, since part of my exploration is trying to learn about language of which I know nothing, I do try to bend them to my pre-conceived notions of what a "good" language allows for. It is very possible that that is ultimately limiting in my ability to embrace the goodness that a language has to offer. I shall try to open my mind.

Quick follow up on "implicit". I definitely use that term in ColdFusion all the time. Looking at the LiveDocs, it looks like I have been using it wrong; I tend to think of "implicit arrays" - but, it looks like the LiveDocs refer to "implicit creation" of arrays... not "implicit arrays." So, again, thanks for the feedback.

StackOverflow and GitHub use backticks to denote inline code. Example:

English English English `code code_code + code * code` English English.

Or, you could just treat any regular code tag-pair where the beginning tag is immediately preceded by a newline and the ending tag is immediately followed by a newline as a block code tag; otherwise, an inline code tag.

I think I'll go with the CODE+NewLine approach. I have a deep-seated dislike of the ` character. Something about it just makes me aggravated. You have to use that character to escape keywords in MySQL. That's probably why I don't like it. You'll probably realize that I'm an emotional coder with syntax hang-ups :)

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.