#! /usr/bin/env ruby #-----------------------------------------------# # Learn to Program # # by Chris Pine # # Copyright (c) 2003-2009 # # chris@pine.fm # #-----------------------------------------------# require 'cgi' require 'stringio' LINKADDR = '/LearnToProgram/' FRLTP_ADDR = 'http://pragmaticprogrammer.com/titles/fr_ltp' class LearnToProgramTutorial @@HLINE = '
'+prettyCode+'' trialRuns.each do |run| if run[:fakeOutput] puts '
'+CGI::escapeHTML(run[:fakeOutput])+'' end if run[:remark] puts '
'+run[:remark]+'
' end output = escapeOutputNotInput(executeCode(code,run[:input])) puts ''+$/+output+'' end nil end # Makes a tag. def method_missing (methodSymbol, attributes = {}) methodName = methodSymbol.to_s attribString = '' attributes.each do |key, val| raise methodName if (key.nil? || val.nil?) attribString += ' '+key.to_s+'="'+val+'"' end if (!block_given?) puts '<'+methodName+attribString+' />' else puts '<'+methodName+attribString+'>' @depth += 1 blockReturn = yield puts blockReturn if (blockReturn.kind_of?(String)) @depth -= 1 puts ''+methodName+'>' end nil end # # TEST PAGE FOR FORMATTING # def generateFormattingPage h1 { 'Heading 1' } h2 { 'Heading 2' } h3 { 'Heading 3' } h4 { 'Heading 4' } para {'Here\'s some code with fake output:'} prog [], '...just kidding, dude...', 'FUNKADELIC!' do <<-'END_CODE' # Here is some 'Ruby' code. # 5 is better than 6. # def wilma do end if not in, dude. #' '#This shouldn\'t cause any problems.' 'Neither # should this\\' 'do end if elsif else case when then while def class' 'or and not next in' 'to 3 or not to 3, that is 3.7' '' + 'x'+''+''+'..' '' +'o' 8 0.09 9.9 5.times {} puts 'I love cheese.' # yo 'g' puts 5.02 + 8 + 0.002 # s'up, muva jimmy = ['yoyoyo', 66] jimmy.each do |item| puts item.inspect end puts case 'pumpkin' when String then 'yep' when Fixnum then 'nope' else 'maybe' end def yummy if (4 <= 5) 'Yummm!' elsif (4 == 5) 'Huh?' else while (1 == 2) puts 'What?' end end end class JustSomeClass def initialize @var = 5 end end puts Math::PI # Should work. puts PI # Shouldn't work. END_CODE end para {'Here\'s some code with input and output:'} prog ['"Chris"', '&26', '
' +
' Table of Contents ' + $/ +
' ' + $/ +
'Chapter 1: Numbers page 1' + $/ +
'Chapter 2: Letters page 72' + $/ +
'Chapter 3: Variables page 118' + $/ +
''
h2 {'Higher Math'}
para do <<-END_PARAGRAPH
(This section is totally optional. It assumes a fair degree
of mathematical knowledge. If you aren't interested, you
can go straight to #{makeLink 'Flow Control', :generateFlowControl}
without any problems. However, a quick look at the section
on Random Numbers might come in handy.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
There aren't nearly as many number methods as there are string methods
(though I still don't know them all off the top of my head). Here, we'll
look at the rest of the arithmetic methods, a random number generator,
and the #{code 'Math'} object, with its trigonometric and transcendental
methods.
END_PARAGRAPH
end
h2 {'More Arithmetic'}
para do <<-END_PARAGRAPH
The other two arithmetic methods are #{code '**'} (exponentiation)
and #{code '%'} (modulus). So if you want to say "five squared"
in Ruby, you would write it as #{code '5**2'}. You can also use
floats for your exponent, so if you want the square root of 5, you
could write #{code '5**0.5'}. The modulus method gives you the remainder
after division by a number. So, for example, if I divide 7 by 3,
I get 2 with a remainder of 1. Let's see it working in a program:
END_PARAGRAPH
end
prog do <<-END_CODE
puts 5**2
puts 5**0.5
puts 7/3
puts 7%3
puts 365%7
END_CODE
end
para do <<-END_PARAGRAPH
From that last line, we learn that a (non-leap) year has some number
of weeks, plus one day. So if your birthday was on a Tuesday this year,
it will be on a Wednesday next year. You can also use floats with the modulus
method. Basically, it works the only sensible way it could... but I'll
let you play around with that.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
There's one last method to mention before we check out the random number
generator: #{code 'abs'}. It just takes the absolute value of the number:
END_PARAGRAPH
end
prog do <<-END_CODE
puts((5-2).abs)
puts((2-5).abs)
END_CODE
end
h2 {'Random Numbers'}
para do <<-END_PARAGRAPH
Ruby comes with a pretty nice random number generator. The method to get
a randomly chosen number is #{code 'rand'}. If you call #{code 'rand'} just like
that, you'll get a float greater than or equal to #{code '0.0'} and less
than #{code '1.0'}. If you give #{code 'rand'} an integer (#{code '5'}
for example), it will give you an integer greater than or equal to
#{code '0'} and less than #{code '5'} (so five possible numbers,
from #{code '0'} to #{code '4'}).
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Let's see #{code 'rand'} in action. (If you reload this page, these numbers will
change each time. You did know I was actually running these programs, didn't you?)
END_PARAGRAPH
end
prog do <<-END_CODE
puts rand
puts rand
puts rand
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(1))
puts(rand(1))
puts(rand(1))
puts(rand(99999999999999999999999999999999999999999999999999999999999))
puts('The weatherman said there is a '+rand(101).to_s+'% chance of rain,')
puts('but you can never trust a weatherman.')
END_CODE
end
para do <<-END_PARAGRAPH
Note that I used #{code 'rand(101)'} to get back numbers from #{code '0'}
to #{code '100'}, and that #{code 'rand(1)'} always
gives back #{code '0'}. Not understanding the range of possible return
values is the biggest mistake I see people make with #{code 'rand'}; even professional
programmers; even in finished products you can buy at the store. I even
had a CD player once which, if set on "Random Play," would play every song but
the last one... (I wonder what would have happened if I had put in a CD with
only one song on it?)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Sometimes you might want #{code 'rand'} to return the same random numbers
in the same sequence on two different runs of your program. (For example, once I
was using randomly generated numbers to create a randomly generated world for a computer
game. If I found a world that I really liked, perhaps I would want to play on it
again, or send it to a friend.) In order to do this, you need to set the
seed, which you can do with #{code 'srand'}. Like this:
END_PARAGRAPH
end
prog do <<-END_CODE
srand 1776
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts ''
srand 1776
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
puts(rand(100))
END_CODE
end
para do <<-END_PARAGRAPH
It will do the same thing every time you seed it with the same number. If you want
to get different numbers again (like what happens if you never use
#{code 'srand'}), then just call #{code 'srand 0'}. This seeds it with a
really weird number, using (among other things) the current time on
your computer, down to the millisecond.
END_PARAGRAPH
end
h2 {"The #{code 'Math'} Object"}
para do <<-END_PARAGRAPH
Finally, let's look at the #{code 'Math'} object. We might as well
jump right in:
END_PARAGRAPH
end
prog do <<-END_CODE
puts(Math::PI)
puts(Math::E)
puts(Math.cos(Math::PI/3))
puts(Math.tan(Math::PI/4))
puts(Math.log(Math::E**2))
puts((1 + Math.sqrt(5))/2)
END_CODE
end
para do <<-END_PARAGRAPH
The first thing you noticed was probably the #{code '::'}
notation. Explaining the scope operator (which is what that is)
is really beyond the, uh... scope of this tutorial. No pun
intended. I swear. Suffice it to say, you can use
#{code 'Math::PI'} just like you would expect to.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
As you can see, #{code 'Math'} has all of the things you would
expect a decent scientific calculator to have. And as always,
the floats are really close to being the right answers.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
So now let's #{makeLink 'flow', :generateFlowControl}!
END_PARAGRAPH
end
end
#
# FLOW CONTROL
#
def generateFlowControl
para do <<-END_PARAGRAPH
Ahhhh, flow control. This is where it all comes together. Even though
this chapter is shorter and easier than the #{makeLink 'methods', :generateMethods}
chapter, it will open up a whole world of programming possibilities.
After this chapter, we'll be able to write truly interactive
programs; in the past we have made programs which say different
things depending on your keyboard input, but after this chapter
they will actually do different things, too. But
before we can do that, we need to be
able to compare the objects in our programs. We need...
END_PARAGRAPH
end
h2 {'Comparison Methods'}
para do <<-END_PARAGRAPH
Let's rush through this part so we can get to the next
section, Branching, where all the cool
stuff happens. So, to see if one object is greater than
or less than another, we use the methods #{code '>'}
and #{code '<'}, like this:
END_PARAGRAPH
end
prog do <<-END_CODE
puts 1 > 2
puts 1 < 2
END_CODE
end
para do <<-END_PARAGRAPH
No problem. Likewise, we can find out if an object is
greater-than-or-equal-to another (or less-than-or-equal-to)
with the methods #{code '>='} and #{code '<='}
END_PARAGRAPH
end
prog do <<-END_CODE
puts 5 >= 5
puts 5 <= 4
END_CODE
end
para do <<-END_PARAGRAPH
And finally, we can see if two objects are equal or not
using #{code '=='} (which means "are these equal?")
and #{code '!='} (which means "are these different?").
It's important not to confuse #{code '='} with #{code '=='}.
#{code '='} is for telling a variable to point at an object
(assignment), and #{code '=='} is for asking the question: "Are
these two objects equal?"
END_PARAGRAPH
end
prog do <<-END_CODE
puts 1 == 1
puts 2 != 1
END_CODE
end
para do <<-END_PARAGRAPH
Of course, we can compare strings, too. When strings
get compared, they compare their lexicographical ordering,
which basically means their dictionary ordering. #{code 'cat'}
comes before #{code 'dog'} in the dictionary, so:
END_PARAGRAPH
end
prog do <<-END_CODE
puts 'cat' < 'dog'
END_CODE
end
para do <<-END_PARAGRAPH
There's a catch, though: the way computers usually do things,
they order capital letters as coming before lowercase letters.
(That's how they store the letters in fonts, for example:
all the capital letters first, then the lowercase ones.)
This means that it will think #{code "'Zoo'"} comes before #{code "'ant'"}, so if you
want to figure out which word would come first in a real dictionary,
make sure to use #{code 'downcase'} (or #{code 'upcase'} or
#{code 'capitalize'}) on both words before you try to compare them.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
One last note before Branching: The comparison
methods aren't giving us the strings #{code "'true'"} and
#{code "'false'"}; they are giving us the special objects #{code 'true'} and
#{code 'false'}. (Of course, #{code 'true.to_s'} gives us
#{code "'true'"}, which is why #{code 'puts'} printed #{code "'true'"}.)
#{code 'true'} and #{code 'false'} are used all the time in...
END_PARAGRAPH
end
h2 {'Branching'}
para do <<-END_PARAGRAPH
Branching is a simple concept, but powerful. In fact, it's so simple
that I bet I don't even have to explain it at all; I'll just show you:
END_PARAGRAPH
end
run1 = {:input => ['Chris']}
run2 = {:input => ['Chewbacca'], :remark => 'But if we put in a different name...'}
progN run1, run2 do <<-END_CODE
puts 'Hello, what\\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'
if name == 'Chris'
puts 'What a lovely name!'
end
END_CODE
end
para do <<-END_PARAGRAPH
And that is branching. If what comes after the #{code 'if'} is
#{code 'true'}, we run the code between the
#{code 'if'} and the #{code 'end'}. If what comes after the
#{code 'if'} is #{code 'false'}, we don't. Plain and simple.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
I indented the code between the #{code 'if'} and the #{code 'end'}
just because I think it's easier to keep track of the
branching that way. Almost all
programmers do this, regardless of what language they are
programming in. It may not seem much help in this simple
example, but when things get more complex, it makes a big
difference.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Often, we would like a program to do one thing if an expression
is #{code 'true'}, and another if it is #{code 'false'}. That's
what #{code 'else'} is for:
END_PARAGRAPH
end
run1 = {:input => ['Chris']}
run2 = {:input => ['Ringo'], :remark => 'Now let\'s try a different name...'}
progN run1, run2 do <<-END_CODE
puts 'I am a fortune-teller. Tell me your name:'
name = gets.chomp
if name == 'Chris'
puts 'I see great things in your future.'
else
puts 'Your future is... Oh my! Look at the time!'
puts 'I really have to go, sorry!'
end
END_CODE
end
para do <<-END_PARAGRAPH
Branching is kind of like coming to a fork in the code: Do
we take the path for people whose #{code "name == 'Chris'"},
or #{code 'else'} do we take the other path?
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
And just like the branches of a tree, you can have branches
which themselves have branches:
END_PARAGRAPH
end
run1 = {:input => ['chris', 'yes']}
run2 = {:input => ['Chris'], :remark => 'Fine, I\'ll capitalize it...'}
progN run1, run2 do <<-END_CODE
puts 'Hello, and welcome to 7th grade English.'
puts 'My name is Mrs. Gabbard. And your name is...?'
name = gets.chomp
if name == name.capitalize
puts 'Please take a seat, ' + name + '.'
else
puts name + '? You mean ' + name.capitalize + ', right?'
puts 'Don\\'t you even know how to spell your name??'
reply = gets.chomp
if reply.downcase == 'yes'
puts 'Hmmph! Well, sit down!'
else
puts 'GET OUT!!'
end
end
END_CODE
end
para do <<-END_PARAGRAPH
Sometimes it might get confusing trying to figure out
where all of the #{code 'if'}s, #{code 'else'}s, and
#{code 'end'}s go. What I do is write the #{code 'end'}
at the same time I write the #{code 'if'}. So
as I was writing the above program, this is how it looked
first:
END_PARAGRAPH
end
prog false do <<-END_CODE
puts 'Hello, and welcome to 7th grade English.'
puts 'My name is Mrs. Gabbard. And your name is...?'
name = gets.chomp
if name == name.capitalize
else
end
END_CODE
end
para do <<-END_PARAGRAPH
Then I filled it in with comments, stuff
in the code the computer will ignore:
END_PARAGRAPH
end
prog false do <<-END_CODE
puts 'Hello, and welcome to 7th grade English.'
puts 'My name is Mrs. Gabbard. And your name is...?'
name = gets.chomp
if name == name.capitalize
# She's civil.
else
# She gets mad.
end
END_CODE
end
para do <<-END_PARAGRAPH
Anything after a #{code '#'} is
considered a comment (unless, of course, you
are in a string). After that, I replaced the comments
with working code. Some people like to leave the comments
in; personally, I think well-written code usually speaks
for itself. I used to use more comments, but the more
"fluent" in Ruby I become, the less I use them. I actually
find them distracting much of the time. It's a personal
choice; you'll find your own (usually evolving) style.
So my next step looked like this:
END_PARAGRAPH
end
prog false do <<-END_CODE
puts 'Hello, and welcome to 7th grade English.'
puts 'My name is Mrs. Gabbard. And your name is...?'
name = gets.chomp
if name == name.capitalize
puts 'Please take a seat, ' + name + '.'
else
puts name + '? You mean ' + name.capitalize + ', right?'
puts 'Don\\'t you even know how to spell your name??'
reply = gets.chomp
if reply.downcase == 'yes'
else
end
end
END_CODE
end
para do <<-END_PARAGRAPH
Again, I wrote down the #{code 'if'}, #{code 'else'}, and
#{code 'end'} all at the same time. It really helps me keep
track of "where I am" in the code. It also makes the job
seem easier because I can focus on one small part, like
filling in the code between the #{code 'if'} and the
#{code 'else'}. The other benefit of doing it this way
is that the computer can understand the program at any
stage. Every one of the unfinished versions of the
program I showed you would run. They weren't finished,
but they were working programs. That way I could test it
as I wrote it, which helped to see how it was coming along
and where it still needed work. When it passed all
of the tests, that's how I knew I was done!
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
These tips will help you write programs with branching,
but they also help with the other main type of flow control:
END_PARAGRAPH
end
h2 {'Looping'}
para do <<-END_PARAGRAPH
Often, you'll want your computer to do the same thing over and
over again—after all, that's what computers are supposed to
be so good at.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
When you tell your computer to keep repeating something,
you also need to tell it when to stop. Computers never get bored,
so if you don't tell it to stop, it won't. We make sure this
doesn't happen by telling the computer to repeat certain parts
of a program #{code 'while'} a certain condition is true. This
works very similarly to how #{code 'if'} works:
END_PARAGRAPH
end
prog ['Hello?', 'Hi!', 'Very nice to meet you.', 'Oh... how sweet!', 'bye'] do <<-END_CODE
command = ''
while command != 'bye'
puts command
command = gets.chomp
end
puts 'Come again soon!'
END_CODE
end
para do <<-END_PARAGRAPH
And that's a loop. (You may have noticed the blank line at the
beginning of the output; it's from the first #{code 'puts'}, before
the first #{code 'gets'}. How would you change the program to get
rid of this first line. Test it! Did it work exactly
like the program above, other than that first blank line?)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Loops allow you to do all kinds of interesting things, as I'm sure
you can imagine. However, they can also cause problems if you
make a mistake. What if your computer gets trapped in an infinite
loop? If you think this may have happened, just hold down the
Ctrl key and press C.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Before we start playing around with loops, though,
let's learn a few things to make our job easier.
END_PARAGRAPH
end
h2 {'A Little Bit of Logic'}
para do <<-END_PARAGRAPH
Let's take a look at our first branching program again. What if
my wife came home, saw the program, tried it out, and it didn't
tell her what a lovely name she had? I wouldn't want to
hurt her feelings (or sleep on the couch), so let's rewrite it:
END_PARAGRAPH
end
prog ['Katy'] do <<-END_CODE
puts 'Hello, what\\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'
if name == 'Chris'
puts 'What a lovely name!'
else
if name == 'Katy'
puts 'What a lovely name!'
end
end
END_CODE
end
para do <<-END_PARAGRAPH
Well, it works... but it isn't a very pretty program. Why not?
Well, the best
rule I ever learned in programming was the DRY rule:
Don't Repeat Yourself. I could probably write a small
book just on why that is such a good rule. In our case, we
repeated the line #{code "puts 'What a lovely name!'"}. Why is
this such a big deal? Well, what if I made a spelling mistake
when I rewrote it? What if I wanted to change it from
#{code "'lovely'"} to #{code "'beautiful'"} on both lines?
I'm lazy, remember? Basically, if
I want the program to do the same thing when it gets
#{code "'Chris'"} or #{code "'Katy'"}, then it should really
do the same thing:
END_PARAGRAPH
end
prog ['Katy'] do <<-END_CODE
puts 'Hello, what\\'s your name?'
name = gets.chomp
puts 'Hello, ' + name + '.'
if (name == 'Chris' or name == 'Katy')
puts 'What a lovely name!'
end
END_CODE
end
para do <<-END_PARAGRAPH
Much better. In order to make it work, I used #{code 'or'}.
The other logical operators are #{code 'and'} and
#{code 'not'}. It is always a good idea to use parentheses
when working with these. Let's see how they work:
END_PARAGRAPH
end
prog do <<-END_CODE
iAmChris = true
iAmPurple = false
iLikeFood = true
iEatRocks = false
puts (iAmChris and iLikeFood)
puts (iLikeFood and iEatRocks)
puts (iAmPurple and iLikeFood)
puts (iAmPurple and iEatRocks)
puts
puts (iAmChris or iLikeFood)
puts (iLikeFood or iEatRocks)
puts (iAmPurple or iLikeFood)
puts (iAmPurple or iEatRocks)
puts
puts (not iAmPurple)
puts (not iAmChris )
END_CODE
end
para do <<-END_PARAGRAPH
The only one of these which might trick you is
#{code 'or'}. In English, we often use "or" to mean
"one or the other, but not both." For example, your
mom might say, "For dessert, you can have pie or cake."
She did not mean you could have them both!
A computer, on the other hand, uses #{code 'or'} to mean "one or the other,
or both." (Another way of saying it is, "at least one of
these is true.") This is why computers are more fun than
moms.
END_PARAGRAPH
end
h2 {'A Few Things to Try'}
para do <<-END_PARAGRAPH
• "99 bottles of beer on the wall..." Write a program
which prints out the lyrics to that beloved classic, that
field-trip favorite: "99 Bottles of Beer on the Wall."
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
• Write a Deaf Grandma program. Whatever you say
to grandma (whatever you type in), she should respond with
#{output 'HUH?! SPEAK UP, SONNY!'}, unless you shout it (type in
all capitals). If you shout, she can hear you (or at least
she thinks so) and yells back, #{output 'NO, NOT SINCE 1938!'} To
make your program really believable, have grandma
shout a different year each time; maybe any year at random
between 1930 and 1950. (This part is optional, and would be
much easier if you read the section on Ruby's random number
generator at the end of the #{makeLink 'methods', :generateMethods}
chapter.) You can't stop talking to grandma
until you shout #{input 'BYE'}.' +
'Beginning "outer block"...' + $/ +
'Beginning "some little block"...' + $/ +
'..."some little block" finished, returning: 5' + $/ +
'Beginning "yet another block"...' + $/ +
'..."yet another block" finished, returning: I like Thai food!' + $/ +
'..."outer block" finished, returning: false' + $/ +
''
para do <<-END_PARAGRAPH
• Better Logger. The output from that last logger was kind
of hard to read, and it would just get worse the more you used it. It would
be so much easier to read if it indented the lines in the inner blocks. To
do this, you'll need to keep track of how deeply nested you are every time
the logger wants to write something. To do this, use a global variable,
a variable you can see from anywhere in your code. To make a global variable,
just precede your variable name with #{code '$'}, like these:
#{code '$global'}, #{code '$nestingDepth'}, and #{code '$bigTopPeeWee'}.
In the end, your logger should output code like this:
END_PARAGRAPH
end
puts '' +
'Beginning "outer block"...' + $/ +
' Beginning "some little block"...' + $/ +
' Beginning "teeny-tiny block"...' + $/ +
' ..."teeny-tiny block" finished, returning: lots of love' + $/ +
' ..."some little block" finished, returning: 42' + $/ +
' Beginning "yet another block"...' + $/ +
' ..."yet another block" finished, returning: I love Indian food!' + $/ +
'..."outer block" finished, returning: true' + $/ +
''
para do <<-END_PARAGRAPH
Well, that's about all you're going to learn from this tutorial.
Congratulations! You've learned a lot! Maybe you don't feel
like you remember everything, or you skipped over some parts... really,
that's just fine. Programming isn't about what you know; it's about
what you can figure out. As long as you know where to find out the
things you forgot, you're doing just fine. I hope you don't think
that I wrote all of this without looking things up every other minute!
Because I did. I also got a lot of help with the code which runs all
of the examples in this tutorial. But where was I looking
stuff up, and who was I asking for help?
#{makeLink 'Let me show you...', :generateBeyond}
END_PARAGRAPH
end
end
#
# BEYOND THIS TUTORIAL
#
def generateBeyond
para do <<-END_PARAGRAPH
So where do we go now? If you have a question, who can you
ask? What if you want your program to open a webpage, send
an email, or resize a digital picture? Well, there are many,
many places to find Ruby help. Unfortunately,
that's sort of unhelpful, isn't it? :-)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
For me, there are really only three places I look for Ruby help.
If it's a small question, and I think I can experiment on my own
to find the answer, I use irb. If it's a bigger question,
I look it up in my pickaxe. And if I just can't figure
it out on my own, then I ask for help on ruby-talk.
END_PARAGRAPH
end
h2 {'IRB: Interactive Ruby'}
para do <<-END_PARAGRAPH
If you installed Ruby, then you installed irb. To use it, just
go to your command prompt and type #{input 'irb'}. When you are
in irb, you can type in any ruby expression you want, and it will tell you
the value of it. Type in #{input '1 + 2'}, and it will tell you
#{output '3'}. (Note that you don't have to use #{code 'puts'}.)
It's kind of like a giant Ruby calculator. When you are done,
just type in #{input 'exit'}.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
There's a lot more to irb than this, but you can learn all about
it in the pickaxe.
END_PARAGRAPH
end
h2 {'The Pickaxe: "Programming Ruby"'}
para do <<-END_PARAGRAPH
Absolutely the Ruby book to get is "Programming Ruby,
The Pragmatic Programmer's Guide", by David Thomas and Andrew
Hunt (the Pragmatic Programmers). While I highly recommend
picking up the
2nd edition
of this excellent book, with all of
the latest Ruby covered, you can also get a slightly older
(but still mostly relevant) version for free online.
(Actually, if you installed the
Windows version of Ruby, you already have it.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
You can find just about everything about Ruby, from the basic
to the advanced, in this book. It's easy to read; it's comprehensive;
it's just about perfect. I wish every language had a book of
this quality. At the back of the book, you'll find a huge section
detailing every method in every class, explaining it and giving
examples. I just love this book!
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
There are a number of places you can get it (including
the Pragmatic Programmers' own site), but my favorite place
is at ruby-doc.org.
That version has a nice table of contents on the side,
as well as an index. (ruby-doc.org has lots of other
great documentation as well, such as for the Core API and
Standard Library... basically, it documents everything Ruby
comes with right out of the box.
Check it out.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
And why is it called "the pickaxe"? Well, there's a picture
of a pickaxe on the cover of the book. It's a silly name, I
guess, but it stuck.
END_PARAGRAPH
end
h2 {'Ruby-Talk: the Ruby Mailing List'}
para do <<-END_PARAGRAPH
Even with irb and the pickaxe, sometimes you still can't figure
it out. Or perhaps you want to know if someone already did
whatever it is you are working on, to see if you could use it
instead. In these cases, the place to go is ruby-talk, the Ruby
Mailing List. It's full of friendly, smart, helpful people.
To learn more about it, or to subscribe, look
here.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
WARNING: There's a lot of mail on the
mailing list every day. I have mine automatically sent to a
different mail folder so that it doesn't get in my way. If you
just don't want to deal with all that mail, though, you don't
have to! The ruby-talk mailing list is mirrored to the newsgroup
comp.lang.ruby, and vice versa, so you can see the same messages
there. Either way, you see the same messages, just in a slightly
different format.
END_PARAGRAPH
end
h2 {'Tim Toady'}
para do <<-END_PARAGRAPH
Something I have tried to shield you from, but which you will
surely run in to soon, is the concept of TMTOWTDI (pronounced
"Tim Toady"): There's More Than One Way To Do It.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Now some will tell you what a wonderful thing TMTOWTDI is, while
others feel quite differently. I don't really have strong feelings
about it in general, but I think it's a terrible way to
teach someone how to program. (As if learning one way to do something
wasn't challenging and confusing enough!)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
However, now that you are moving beyond this tutorial, you'll
be seeing much more diverse code. For example, I can think of
at least five other ways to make a string (aside from surrounding
some text in single quotes), and each one works slightly differently.
I only showed you the simplest of the six.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
And when we talked about branching, I showed you #{code 'if'},
but I didn't show you #{code 'unless'}. I'll let you figure
that one out in irb.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Another nice little shortcut you can use with #{code 'if'},
#{code 'unless'}, and #{code 'while'}, is the cute one-line version:
END_PARAGRAPH
end
prog do <<-END_CODE
# These words are from a program I wrote to generate
# English-like babble. Cool, huh?
puts 'grobably combergearl kitatently thememberate' if 5 == 2**2 + 1**1
puts 'enlestrationshifter supposine follutify blace' unless 'Chris'.length == 5
END_CODE
end
para do <<-END_PARAGRAPH
And finally, there is another way of writing methods which take blocks
(not procs). We saw the thing where we grabbed the block and turned
it into a proc using the #{code '&block'} trick in your parameter list
when you define the function. Then, to call the block, you just use
#{code 'block.call'}. Well, there's a shorter way (though I personally
find it more confusing). Instead of this:
END_PARAGRAPH
end
prog do <<-END_CODE
def doItTwice(&block)
block.call
block.call
end
doItTwice do
puts 'murditivent flavitemphan siresent litics'
end
END_CODE
end
para do <<-END_PARAGRAPH
...you do this:
END_PARAGRAPH
end
prog do <<-END_CODE
def doItTwice
yield
yield
end
doItTwice do
puts 'buritiate mustripe lablic acticise'
end
END_CODE
end
para do <<-END_PARAGRAPH
I don't know... what do you think? Maybe it's just me, but...
#{code 'yield'}?! If it was something like #{code 'call_the_hidden_block'}
or something, that would make a lot more sense to me.
A lot of people say #{code 'yield'} makes sense to them. But
I guess that's what TMTOWTDI is all about: they do it their way,
and I'll do it my way.
END_PARAGRAPH
end
h2 {'THE END'}
para do <<-END_PARAGRAPH
Use it for good and not evil. :-) And if you found this tutorial
useful (or confusing, or if you found an error),
let me know!
END_PARAGRAPH
end
end
# menu helpers
def menuBookLink
para(:class=>'funnyMenuText fancyMenuText', :style=>'font-size: 12px; font-family: times, serif;') { 'the' }
para(:class=>'funnyMenuText fancyMenuText', :style=>'font-size: 12px; font-family: times, serif;') { 'improved' }
para(:class=>'funnyMenuText fancyMenuText', :style=>'font-size: 12px; font-family: times, serif; letter-spacing: 6px; font-variant: small-caps;') { 'expanded' }
para(:class=>'funnyMenuText fancyMenuText', :style=>'font-size: 12px; font-family: times, serif;') { 'version' }
puts ""
img(:width=>'100', :height=>'120', :src=>'/images/LTP_cover.jpg')
puts ''
end
def menuTOC
para(:class=>'funnyMenuText') { '« the original tutorial »' }
ol(:start=>'0', :style=>'padding-top: 15px; padding-bottom: 15px;') do
@chapters.sort_by{|x| x[0]}.each do |aChapNum, aChapter|
if aChapNum != 'format'
li { makeLink(aChapter[0],aChapter[1]) }
end
end
end
para do <<-END_PARAGRAPH
(Japanese translation
by Shin Nishiyama.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
(French translation
by Jean‑Pierre ANGHEL.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
(Russian translation
by Mikhail Shokhirev.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
(Danish translation
by Gunner Carstens.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
(Brazilian Portuguese translation
by Fabio Akita et al.)
END_PARAGRAPH
end
end
#
# MAIN PAGE GENERATION
#
def generate
srand
chapNum = @cgi.params['Chapter'][0]
chapter = @chapters[chapNum]
chapTitle = 'Learn to Program, by Chris Pine'
if chapter
chapTitle = chapNum + '. ' if chapNum < 'A'
chapTitle.sub! /^0/, ''
chapTitle += chapter[0]
end
puts ''
html(:lang=>'en') do
head do
link(:href=>'/stylesheets/pine.css', :rel=>'Stylesheet', :type=>'text/css', :media=>'screen')
link(:href=>LINKADDR+'tutorial.css', :rel=>'Stylesheet', :type=>'text/css', :media=>'screen')
title { chapTitle }
script(:language=>'JavaScript', :src=>'http://www.gvisit.com/record.php?sid=6941c11eba5c874197e2096f9c854106', :type=>'text/javascript') {}
end # head
body do
div(:id=>'pageWidth') do
div(:id=>'headerBar') do
div(:id=>'titlePicContainer') do
puts ''
img(:id=>'titlePic', :width=>'418', :height=>'108', :src=>'/images/titleLTP.gif', :alt=>'Learn to Program')
puts ''
end
puts ''
puts '
'
puts ''
end
div(:id=>'menuPane') do
img(:id=>'menuSpearTop', :width=>'35', :height=>'38', :src=>'/images/spearup_sm.gif')
menuBookLink
img(:width=>'64', :height=>'21', :style=>'padding: 30px;', :src=>'/images/swirly.gif')
menuTOC
img(:id=>'menuSpearBottom', :width=>'36', :height=>'40', :src=>'/images/speardown_sm.gif')
end
div(:id=>'contentPane') do
if chapter
h1 {chapTitle}
puts @@HLINE
method(chapter[1]).call
else # TOC
h2 { 'A Place to Start for the Future Programmer' }
para do <<-END_PARAGRAPH
I guess this all began back in 2002. I was thinking
about teaching programming, and what a great language
Ruby would be for learning how to program. I mean, we were
all excited about Ruby because it was powerful, elegant, and
really just fun, but it seemed to me that it would also
be a great way to get into programming in the first place.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Unfortunately, there wasn't much Ruby documentation
geared for newbies at the time. Some of us
in the community were talking about what such a
"Ruby for the Nuby" tutorial would
need, and more generally, how to teach programming at all.
The more I thought about this, the more I had to say (which
surprised me a bit). Finally, someone said, "Chris,
why don't you just write a tutorial instead of talking about
it?" So I did.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
And it wasn't very good. I had all these ideas that were good
in theory, but the actual task of making a great
tutorial for non-programmers was vastly more challenging than
I had realized. (I mean, it seemed good to me, but I already
knew how to program.)
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
What saved me was that I made it really easy for people to
contact me, and I always tried to help people when they got
stuck. When I saw a lot of people getting stuck in one place,
I'd rewrite it. It was a lot of work, but it slowly got better
and better.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
A couple of years later, it was getting pretty good. :-) So
good, in fact, that I was ready to pronounce it finished, and
move on to something else. And right about then came an
opportunity to turn the tutorial into a book. Since it was
already basically done, I figured this would be no problem.
I'd just clean up a few spots, add some more exercises, maybe
some more examples, a few more chapters, run it by 50 more
reviewers...
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
It took me another year, but now I think it's really
really good, mostly because of the hundreds of
brave souls who have helped me write it.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
What's here on this site is the original tutorial, more or less
unchanged since 2004. For the latest and greatest, you'll
want to check out the book.
END_PARAGRAPH
end
puts @@HLINE
h2 { 'Thoughts For Teachers' }
para do <<-END_PARAGRAPH
There were a few guiding principles that I tried to stick to.
I think they make the learning process much smoother;
learning to program is hard enough as it is. If you're
teaching or guiding someone on the road to hackerdom, these
ideas might help you, too.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
First, I tried to separate concepts as much as possible,
so that the student would only have to learn
one concept at a time. This was difficult at first, but a little
too easy after I had some practice. Some things must be
taught before others, but I was amazed at how little of
a precedence hierarchy there really is. Eventually, I just had to
pick an order, and I tried to arrange things so that each
new section was motivated by the previous ones.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Another principle I've kept in mind is to teach only one way
to do something. It's an obvious benefit in a tutorial for
people who have never programmed before. For one thing,
one way to do something is easier to learn than two. Perhaps
the more important benefit, though, is that the fewer things
you teach a new programmer, the more creative and clever
they have to be in their programming. Since so much of programming
is problem solving, it's crucial to encourage that as much
as possible at every stage.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
I have tried to piggy-back programming concepts onto concepts
the new programmer already has; to present ideas in such a way
that their intuition will carry the load, rather than the
tutorial. Object-Oriented programming lends itself to this
quite well. I was able to begin referring to "objects" and
different "kinds of objects" pretty early in the tutorial,
slipping those phrases in at the most innocent of moments.
I wasn't saying anything like "everything in Ruby is an
object," or "numbers and strings are kinds of objects,"
because these statements really don't mean anything to
a new programmer. Instead, I would talk about strings
(not "string objects"), and sometimes I would refer to
"objects", simply meaning "the things in these programs."
The fact that all these things in Ruby are objects
made this sort of sneakiness on my part work so well.
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
Although I wanted to avoid needless OO jargon, I wanted
to make sure that, if they did need to learn a word, they
learned the right one. (I don't want them to have to learn
it twice, right?) So I called them "strings," not "text." Methods
needed to be called something, so I called them "methods."
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
As far as the exercises are concerned, I think I came up
with some good ones, but you can never have too many.
Honestly, I bet I spent half of my time just trying to
come up with fun, interesting exercises.
Boring exercises absolutely kill any desire
to program, while the perfect exercise creates an itch
the new programmer can't help but scratch. In short,
you just can't spend too much time coming up with good
exercises.
END_PARAGRAPH
end
puts @@HLINE
h2 { 'About the Original Tutorial' }
para do <<-END_PARAGRAPH
The pages of the tutorial (and even this page) are generated by a
big Ruby program,
of course. :-)
As such, it has some neat features. For example, all of the
code samples are actually being run every time you view
the page, and the output shown is the output they generate.
I think this is the best, easiest, and
certainly the coolest way to make sure that all of the
code I present works exactly as I say it does.
You don't have to worry that I might have copied the output
of one of the examples
wrong, or forgotten to test some of the code; it's all tested
every time you see it. So in the section on random number
generators, if you reload the page you will see the numbers
change each time... nice.
(I used a similar trick for the example code when writing
the book, but it's obviously more apparent with the tutorial.)
END_PARAGRAPH
end
para do
''+
'
'+
''
end
puts @@HLINE
h2 { 'Acknowledgements' }
para do <<-END_PARAGRAPH
Finally, I'd like to thank everyone on the ruby-talk mailing list
for their thoughts and encouragement, all of my wonderful
reviewers for their help in making the book far better than
I could have alone, my dear wife especially
for being my main reviewer/tester/guinea pig/muse,
Matz for creating this fabulous language, and the Pragmatic Programmers
for telling me about it—and, of course, for publishing
my book!
END_PARAGRAPH
end
para do <<-END_PARAGRAPH
If you notice any errors or typos, or have any comments or
suggestions or good exercises I could include, please
let me know.
END_PARAGRAPH
end
end
puts @@HLINE
para(:style=>'padding-bottom: 20px;') { "© 2003-#{Time.now.year} Chris Pine" }
end # contentPane
end # pageWidth
end # body
end # html
end
def self.handle_request cgi
begin
if cgi.params['ShowTutorialCode'][0]
tutorialSource = File.read __FILE__
cgi.out('text/plain') { tutorialSource }
else
page = self.new cgi
page.generate
page.out
end
rescue Exception => e
error_msg = <<-END_ERROR
#{e.class}: #{CGI::escapeHTML(e.message)}
#{e.backtrace.join("\n")}
END_ERROR
cgi.out { error_msg }
end
end
end