"Idiotic Ruby".sub( /ot/, 'omat')
Toby DiPasquale
Why Learn Ruby?
- Ruby is easy to read
- Completely object-oriented
- Ruby tries to follow POLS (Principle of Least Surprise)
- Ruby has a REPL (Read-Eval-Print Loop), which makes prototyping very fast
- Brings you close to power of LISP without all the parentheses
- Bruce Tate thinks Ruby is the next big language (with Rails driving initial adoption)
Conventions
p prints human-readable representation of object
pp does the same thing, but nicer format
# -> val indicates that val is the output of the preceding statement
Told you it was easy to read...
abort unless str.include? "Barracus"
until works like while not
x = x * 2 until x > 100
Want to swap two values?
x, y = y, x
Common Globals
__FILE__ is the name of the current source file
$0 at the top level is the name of the top-level program being executed
if __FILE__ == $0
# ...
end
$: is an array of paths to look for Ruby scripts to load
$! is the current Exception passed to raise
Common Globals (cont)
$SAFE is the current safe level (used to sandbox code)
ENV is a Hash of environment variables
ARGV is an Array of command-line args (synonym for $*)
- If a variable called
SCRIPT_LINES__ is defined to be a Hash, it will be filled with the source of each file it parses (key is filename)
Method Name Qualifiers
- Methods ending in ? are predicates (Boolean queries)
if File.readable? "/etc/password"
PasswordCracker.run "/etc/password"
end
Methods ending in ! are dangerous, meaning that they modify the receiver
str = " hello world "
str.strip!
p str # -> "hello world"
These qualifiers are optional but make code very readable
OR-equal Operator
||= will only assign if the left-hand side is nil
s = ""
# ...
s ||= "hello" # -> ""
The following two lines are equivalent:
s ||= "hello"
s = "hello" if s.nil?
I've been told that Perl has the same faculty
Attributes (Instance Variables)
- By default, object attributes can't be read or written to outside of the object
- You can change the access on attributes with
attr_reader, attr_writer and attr_accessor
- These are class methods that open the named attributes up to reading, writing or read/write, resp.
- Ever write a Java Bean? Here's Ruby's:
class RubyBean
attr_accessor :name, :date, :location
end
Attributes (cont.)
- You can create virtual attributes, as well
class RubyBean
attr_reader :name, :date, :location
def name=(str)
@name = str.capitalize
end
end
x = RubyBean.new
x.name = "frank schlobatnik"
p x.name # -> "Frank Schlobatnik"
Exceptions
- Catch exceptions with
begin and rescue
begin
File.open("/etc/passwd") do |f|
puts f.gets
end
rescue => e
$stderr.puts "error: #{e.message}"
end
The => puts the Exception object into variable e
Exceptions (cont.)
ensure makes sure code is called regardless of outcome
f = nil
begin
f = File.open "/etc/passwd"
# ... process f here ...
rescue => e
$stderr.puts "error: #{e.message}"
ensure
f.close unless f.nil?
end
f is guaranteed to be closed when its over
Exceptions (cont.)
- Raise your own exceptions with
raise
raise can be used with String or Exception objects
raise
raise StandardError.new
raise "failure to locate resource #A8FD01-9"
fail is an alias for raise; they can be used interchangably
Some exceptions can't be caught unless explicitly asked for in rescue (check Pickaxe book for more info here)
Optional Parameters
- Parameters that, if not specified, take on some default value
def somemethod(x, y=nil)
return x * y unless y.nil?
x
end
This prevents you from having to write multiple instances of the same method (DRY)
Rest Parameters
- Batches up extraneous parameters into an array
def substitute(re, str, *rest)
rest.each { |r| r.gsub( re, str) }
end
Allows creation of methods that take variable numbers of arguments
printf and family use this technique
Keyword Parameters
- Ruby doesn't have them! (yet)
- But they can be emulated with a Hash (keys are Symbols)
def method(req, opt={})
# look for keyword args in opt hash
end
Old calls to method don't need to know or care about new keyword parameters
Must make sure updated method doesn't handle old parameters differently
Regular Expressions
- Ruby has first-class support for regexps
- Syntax was largely lifted from Perl
if "hello" =~ /ell/
puts "hot damn! my regexp matched!"
end
!~ is the compliment to =~ operator
Perl extended regular expression syntax is supported, as well
Regular Expressions (cont)
- After matching strings, they are most often used for substituting patterns of text
"hello".sub /o/, ' no' # -> "hell no"
sub only substitutes first match; gsub subs them all
def no_vowels(str)
str.gsub /[aeiou]/, '!'
end
no_vowels "hello world" # -> "h!ll! w!rld"
Regular Expressions (cont)
- You can do match grouping, too
def obfuscate_cc(num)
if num =~ /\d\d\d\d-\d\d\d\d-\d\d\d\d-(\d\d\d\d)/
"XXXX-XXXX-XXXX-#{$1}"
else
"invalid card number"
end
end
puts obfuscate_cc "1234-5678-9012-3456"
Output:
"XXXX-XXXX-XXXX-3456"
Modules and Mix-ins
- Ruby has single inheritance only, so instead, they have "mix-ins"
Modules can't be instantiated, but their methods and data can be made available to other classes and objects
- Defining a module is very similar to a class
module X
# ...
end
class Y
include X
# ...
end
Modules and Mix-ins (cont)
- Notice I said classes and objects; here's how to mix-in a Module to an object...
obj.extend MyModule
...or add functionality to a specific object instance
class <<obj
def newmethod
puts "I'm not in any other object of this class"
end
end
Has many names: virtual classes, eigenclasses, singleton objects, etc
Blocks
- Blocks are functions that can be passed to other functions
- A method yields (producer) to a block (consumer)
- Method concentrates on producing values
- Block can be interchanged to consume values as needed
- Single line blocks are written with
{ |foo| ... }
- Multiline blocks are written with
do |foo| ... end
- Blocks make it easy to implement iterators, callbacks, transactions, etc
Iterators
- Use a block to iterate over values with on-demand code
def fibonacci(max)
x, y = 1, 1
while x <= max
yield x
x, y = y, x + y
end
end
fibonacci 30 { |x| print x, " " } # -> 1 1 2 3...
y = 0
fibonacci 30 { |x| y += x }
p y # -> 54
Transactions
- Open a file, pass each line to block and make sure its closed
def each_line(file)
f = nil
begin
f = File.open(file)
while line = f.gets
next if line =~ /^#/
yield line
end
rescue => e
raise e
ensure
f.close unless f.nil?
end
end
Closures
- A closure is a function that remembers the context in which it was created
- They can be useful to implement callbacks
class ButtonController
def initialize(label, &action)
@label = label
@action = action
end
def press
@action.call @label
end
end
# ...
start = ButtonController.new "Start" { thread.start }
pause = ButtonController.new "Pause" { thread.pause }
Closures (cont)
start and pause work even when thread goes out of scope
- Closure's environment is a reference, not a copy (more on this later)
- Most commonly created with blocks or
lambda method
def mkcounter(a)
lambda { a += 1; puts "#{a}" }
end
lambda creates a Proc object with the associated block
Duck Typing
- Calling methods on objects based on desired behavior, not class
- For this to work, need consistent method naming and behavior
<< is a good example
x << "yet another line"
This code works whether x is a String, Array or IO
You should learn this if only because lots of code is written in this style
Duck Typing (cont)
- Don't use
instance_of?; use respond_to?
if x.respond_to? :<<
x << y
elsif x.respond_to? :append
x.append y
else
raise "no way to append"
end
You can make an object that acts like another, but does something completely different under the hood
Can make program extension and refactoring easier
method_missing
method_missing will capture any method call for which the receiver has no predefined method
class Roman
def romanToInt(str)
# ...
end
def method_missing(methId)
str = methId.id2name
romanToInt str
end
end
r = Roman.new
r.iv # -> 4
r.xxiii # -> 23
r.mm # -> 2000
No Ruby app server could work without this method
Continuations
- Continuations are the saved state of a program
- Calling a continuation brings execution back to right after it was created
callcc takes a block and passes a Continuation object to it
- Often described as a "goto with arguments"
- When asked why he put in continuations but not macros, Matz said "the people who'd make an awful mess with macros wouldn't even dare to touch continuations"
Thanks!
- Thanks for coming out!
- Even though the Mulder clan has a collective aneurism every time they hear me say it, this is Philly on Rails