"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"
Module
s 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