Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Ryan Jeffords

Seven Languages In Seven Weeks: Ruby - Day 1

By Ben Nadel on
Tags: Ruby

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 World
Hello World
Hello World
Hello World
Hello 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:

7
7
7

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.

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

Ben
Ben
Ben
Ben
Ben
Ben
Ben
Ben
Ben
Ben
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
Benjamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
B-Jamin
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Big Ben
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B
Grand Master B

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 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
This is sentence number 6
This is sentence number 7
This is sentence number 8
This is sentence number 9
This is sentence number 10
This is sentence number 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
This is sentence number 6
This is sentence number 7
This is sentence number 8
This is sentence number 9
This is sentence number 10
This is sentence number 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
This is sentence number 6
This is sentence number 7
This is sentence number 8
This is sentence number 9
This is sentence number 10
This is sentence number 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
This is sentence number 6
This is sentence number 7
This is sentence number 8
This is sentence number 9
This is sentence number 10
This is sentence number 1
This is sentence number 2
This is sentence number 3
This is sentence number 4
This is sentence number 5
This is sentence number 6
This is sentence number 7
This is sentence number 8
This is sentence number 9
This 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 10
Guess: 1
...Guess higher!
Guess the number between 1 and 10
Guess: 2
...Guess higher!
Guess the number between 1 and 10
Guess: 3
...Guess higher!
Guess the number between 1 and 10
Guess: 4
...Guess higher!
Guess the number between 1 and 10
Guess: 5
...Guess higher!
Guess the number between 1 and 10
Guess: 6
...Guess higher!
Guess the number between 1 and 10
Guess: 7
Heck 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 10
Guess: 1
...Guess higher!
Guess the number between 1 and 10
Guess: 2
Heck 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?

Tweet This Great article by @BenNadel - Seven Languages In Seven Weeks: Ruby - Day 1 Thanks my man — you rock the party that rocks the body!



Reader Comments

Ben, you can do a for loop in Ruby:

for i in 0..5
puts "Value of local variable is #{i}"
end

It's just that most Rubyists prefer the (to my eye) nicer approaches such as range.

Reply to this Comment

@Hal,

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.

Reply to this Comment

Hi Ben,

If you're trying to specify an increment using a for loop in Ruby, you can do something like this:

for i in (1..10).step(2)
puts i
end

Michael

Reply to this Comment

@Ben

To use a step-change on your range you can do:

(0..10).step(2) do |i|
puts "Num: #{i}"
end

Which prints:

Num: 0
Num: 2
Num: 4
Num: 6
Num: 8
Num: 10

Reply to this Comment

@Stephen, @Michael,

Thanks to both of you :) One thing I can't quite figure out, however, is when the {/} notation is supported in addition to a do/end or something like that.

For example, I can do this:

1.upto(10){ ... }
1.upto(10) do ... end

But, I can't seem to do the same for the for() loop.

for i in (1..10) do ... end

... works, but the same with {/} seems to fail. Are there just times when the two cannot be interchanged?

Reply to this Comment

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.

Reply to this Comment

Hey Ben,

I'll be interested to see how you get on with the book, seems a lot to wrap your head around in 7 weeks.

Out of interest, do you comment production code the same way you comment code samples on your blog. I find it strangely hard to read!!

Phil

Reply to this Comment

@Andrew,

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!

Reply to this Comment

@Ben,

TextMate is really nice to use with Ruby and my own personal preference. It's definitely worth checking out if you haven't used it before.

Michael

Reply to this Comment

@Ben,

I was just interested in your commenting in general, I see you add a lot in on your CF posts too.

I use CBuilder at work but at home I run Aptana 3 (beta) which has all the support for Ruby and Rails, might want to check it out. Maybe install on CBuilder as a plugin.

Phil

Reply to this Comment

@Michael,

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.

Reply to this Comment

Interesting post, Ben, thanks!

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!)

Reply to this Comment

@Rick,

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.

Reply to this Comment

I really like the unless and if operators used like that.

Here was some code I was doing last night:

@super_programmer = SuperProgrammer.find_by_last_name( "Nadel" )
return false if @super_programmer.nil?

Now, we know that given this *particular* search, it would never return false, but don't you find that pretty readable?

Reply to this Comment

@Hal,

Ha ha :) I will agree that with in the context of return(), it does feel better; it's probably something that just needs some getting used to.

Reply to this Comment

Ben,

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.

A few minor points:

- Strings with double-quotes are interpolated, not evaluated - there is no evaluation going on. Rather, the Ruby interpreter takes your nice clean code

  • puts "Hello, #{name}!"

and, under the hood, simply pretends it was originally written as

  • puts('Hello' + name + '!')

.

- 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,

  • 3

is a numeric literal while

  • 3 + 2

is not.

- Regular-expression literal notation (regular expressions using the slashes, e.g.

  • /hello(\s+world)?/

) are far older than Ruby and JavaScript. I don't know the history, but the notation is used in Perl quite a lot.

- The three-clause

  • for

is, like

  • goto

, rather low-level: Computerese, rather than English. If you're writing in Ruby because you like writing in English, then you probably would not be using the three-clause

  • for

anyway. (Granted, Ruby does have its own share of low-level, and even supremely awkward, constructs. Don't use them: instead, just try to write in natural English.)

Cheers!

Reply to this Comment

Hal,

If you want to go even more idiomatically Ruby, you don't even need to use "if abc.nil?" - you can use "unless abc" (so long as abc won't be a Boolean value).

  • @super_programmer = SuperProgrammer.find_by_last_name( "Nadel" )
  • return false unless @super_programmer

Cheers!

Reply to this Comment

Ben,

Looks like my comment to you got ... formatted.

Any chance you've got support for inline code formatting that will just format code, inline with the rest of the text, in a monospace font, rather than turning it into its own separate paragraph?

Cheers!

Reply to this Comment

@Justice,

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.

Joseph actually just made some similar points about the Io language:

http://www.bennadel.com/blog/2064-Seven-Languages-In-Seven-Weeks-Io-Day-1.htm

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.

Reply to this Comment

@Justice,

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.

Reply to this Comment

Ben,

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.

Cheers!

Reply to this Comment

@Justice,

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 :)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.