Friday, August 11, 2006

Ruby tutorial code

I gave a little talk about Ruby to folks here at Sharpcast and thought I'd share the handout I prepared. It covers most of the basics of Ruby.

#!/bin/env ruby
# Ruby basics by Adam S. Rosien. 2-Aug-2006.

# Comments are prefixed by #

# Everything is an object. No primitives. nil is even an object.
#
# Methods are modeled as messages. This is known as "duck" typing:
# "If it walks like a duck and quacks like a duck, it must be a duck."
# Interfaces are kind of moot; you just send a message and see if it responds.
# Method don't require parenthesis unless needed for an ambiguity.
#
# Some naming conventions:
# ClassNames
# method_names and variable_names
# methods_asking_question? return true or false
# slightly_dangerous_method! that might have side-effects
1.to_s # '1'
'foo'.nil? # false
nil.nil? # true
' foo '.strip! # modifies string in place, removing leading/trailing spaces

# What's an object's class?
'foo'.class # String
'foo'.class.class # Class
'foo'.class.class.class # Class

# Traverse the class hierarchy.
String.superclass # Object
Object.superclass # nil

# Symbols: objects used to represent unique names in the intepreter.
asym = :sym

# Other reflection: You can query an object about messages and send them, i.e., make a method call.
'foo'.respond_to? :asdf # false
'foo'.respond_to? :length # true
'foo'.send :length # 3

# Basic types
i = 500
f = 500.1
s = 'I want my MTV on channel 9.'
# Use double quotes and #{} to expand template.
s = "I want my MTV on channel #{i}." # "I want my MTV on channel 500"

# Arrays
array = [1, 2, 3]
array.size # 3
array[1] # 2

# Hashes, i.e., maps
hash = { 'foo' => 'bar', 'baz' => 999 }
hash['foo'] # 'bar'
hash['baz'] # 999
hash[1234] # nil

# Classes
class Foo
# Define a class method.
def Foo.all_the_time
"Foo all the time!"
end

# Called when object is created.
def initialize
# Instance variables are prefixed with '@'.
@bar = 'bar'
end

# Define a method named bar that returns the value of the instance variable @bar.
def bar
# No 'return' needed. Methods return the value of the last expression.
@bar
end
end

# Objects are created by calling 'new' on their class.
foo = Foo.new

# Instance method.
foo.bar # 'bar'

# Class method.
Foo.all_the_time # 'Foo all the time!"

# You can define a method on an individual object! (Known as "singleton method")
def foo.baz
'baz'
end
foo.baz # 'baz'

# You can even add or change methods on any other class! (This can be a good thing.)
class String
# self is equivalent to 'this' in Java or C++.
# to_s returns the string representation of an object.
def wiggly
"~~~#{self.to_s}~~~"
end
end
"ruby".wiggly # '~~~ruby~~~'

# If an object doesn't respond to a message, the 'method_missing' method is called.
# You can override this method in your classes for magical behavior.
class Foo
# sym is the name of the method called, args is an array of arguments.
def method_missing sym, *args
puts "I can't #{sym}!"
end
end

Foo.new.throw_ninja_stars # "I can't throw_ninja_stars!"

# Inheritance
class Bar < Foo
end

# Blocks: the most useful feature of Ruby.
# A block is passed to a method which may 'yield' values to it.

# The each method of an array takes a block, and yields each element of the array to it.
# Using braces block syntax.
array.each {|elem| puts elem } # 1
# 2
# 3

# Using do-end block syntax.
array.each do |elem|
puts elem
end # 1
# 2
# 3

# Defining a method that takes a block:
class Foo
# Yield the value of @bar to the block three times.
def bar_three_times
yield @bar
yield @bar
yield @bar
end
end
Foo.new.bar_three_times {|bar| puts bar} # bar
# bar
# bar

# Useful methods that take blocks.
[1,2,3].map {|elem| elem + 1} # [2,3,4]
[1,2,3].find_all {|elem| elem % 2 == 0} # [2]
[1,2,3].reject {|elem| elem % 2 == 0} # [1,3]
[3,1,6,3,6,4,8].sort.uniq # [1,3,4,6,8]
['foosieseses', 'barsieses', 'bazzies'].sort_by {|elem| elem.length} # ["bazzies", "barsieses", "foosieseses"]

# Inject takes an initial value for an accumulator, passing the accumulator and element to the block.
# The accumulator is set to the value the block returns.
[1,2,3].inject(0) {|sum, elem| sum + elem} # 6
[2,3,4].inject(1) {|prod, elem| prod * elem} # 24

{'a' => 1, 'b' => 2}.each_pair {|k,v| puts "#{k} = #{v}"} # a = 1
# b = 2
# Metaprogramming: powerful mojo. Code that writes code.
# Why? So you don't have to.

# It's a pain to have to write getters and setters for instance variables.
# Ruby defines helper class methods to do this for you.
class Foo
attr_reader :bar # Defines method 'bar' that returns @bar.
attr_writer :bar # Defines method 'bar=' that sets @bar to a value.
attr_accessor :bar # Reader and writer.
end
f = Foo.new
f.bar # 'bar'
f.bar = 'foo'
f.bar # 'foo'

# How might these kinds of methods be written?
class Bar
# The *syms says that there are a variable number of method arguments,
# so just turn them into an array.
def Bar.attr_reader *syms
syms.each do |sym|
# Evaluate this string as ruby in the context of the current class.
class_eval "def #{sym}; @#{sym}; end"
end
end

def initialize
@asdf = 'asdf'
@ghij = 'ghij'
end

attr_reader :asdf, :ghij
end
b = Bar.new
b.asdf # 'asdf'

# You can also evaluate a string as ruby in the context of an instance, rather than a class.
b.instance_eval "asdf" # 'asdf'

1 Comments:

Anonymous Anonymous said...

I like it - easier to understand than most other explanations

7:38 AM  

Post a Comment

<< Home