Advanced Ruby - Day 1
Let’s get started. I’m at the Advanced Ruby training. Here are the topics we gonna cover
- Blocks, Procs, and Closures
- Ruby 1.9
- Your Own, Private Ruby
- Design in a Dynamic Language
- Messin’ with Types
- …make it fast.
- The Ruby Object Model
- Metaprogramming
- Making Domains-Specific Languages
- Concurrency
- Exotic Control Flow
- Library Organization
- Debugging and Profiling
- JRuby
- Ruby Extras
- Distributed Programming
- Onward and Upward!
In fact when I saw the topics that will be covered by Dave Thomas and Chad Fowler, and the fact that some of my collogues from Pinnacol where attending I had to join…Oh yea, it’s a 25 minute drive from home too.
So this morning I woke up and installed Ruby 1.9. I didn’t use multiruby as many do I just downloaded Ruby 1.9.1-p0 from http://www.ruby-lang.org/en/downloads. These are the steps to install:
cd ruby-1.9.1-p0
autoconf
./configure —prefix=/usr/local/ruby1.9
make
sudo make install
Then you can check the version by
$/usr/local/ruby1.9/bin/ruby -v ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0] $/usr/local/ruby1.9/bin/irb RUBY_VERSION => "1.9.1"
I didn’t add /usr/local/ruby1.9/bin to my path, so the default ruby is still my mack default, 1.8.6 of my mac (ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]).
Keep on reading to see some of the code we are walking through. This said it’s hard to convey how much value Chad and Dave are passing along with all their deep explanations of each of the examples…
Objectives
Deep
The “Why” not just the “How”
Explore Ruby’s Lesser-kown Corners
Pragmatic, not Dogmatic
Discussion for Highest Group Value
Blocks, Procs, and Closures
Blocks
one two {…} # binds to #two, and result of that is passed to one
one two do … end # block bind to #one, and two is passed to as argument.
Built-in support:
- block_given?
- loop
- yield
- break, redo, next, retry
def show_break
puts "before yield"
yield
puts "after yield" # never make it here
end
puts "before call"
result = show_break do
puts "before break"
break 123
puts "after break" # never make it here
end
puts "after call, result = #{result}"The output of the above code is
before call before yield before break after call, result = 123
Then Dave and Chad explain in details the next, redo, and retry keywords. Note retry is removed in 1.9.
Blocks Into Objects
def show_block_param(&block)
puts block.inspect # use the blokc
[1,2,3].each &block # pass the block along
puts block.call
yield
end
show_block_param { puts "in block"}Using lambda, proc, and Proc.new
- 1.8: proc and lambda are synonyms
- 1.9: proc and Proc.new are synonyms
So don’t use proc.
Lambda closer to method calling
Proc.new uses parallel assignment rules for parameter passing
Proc.new { |a,b| }.call(1,2,3) # OK
lambda { |a,b| }.call (1) # ArgumentError: wrong number of args (1 for 2)
Dave goes onto the parameters and yield, followed by Return form blocks.
def meth(&block)
block.call
end
b = Proc.new { return 99 }
puts meth(&b) # LocalJumpError: unexpected returnBindings
Encapsulate the context at a point of execution
class A
attr_writer :ivar
def initialize
@ivar = 123
end
def get_binding
ivar = "ivar"
binding
end
end
a = A.new
bind = a.get_binding { "block value" }
eval "puts ivar", bind # => ivar
eval "puts @ivar", bind # => 123
a.ivar = "new value"
eval "puts ivar", bind # => ivar
eval "puts @ivar", bind # => new valueClosures
Blocks carry with them the binding in which they were created
def block_in_sandbox(param)
ivar = "local variable"
lambda do
puts "param = #{param}"
puts "ivar = #{ivar}"
end
end
block = block_in_sandbox(99)
block.call
# param = 99
# ivar = local variableThen we moved onto an exercise to create a counter with what we just learned:
require 'test/unit'
def counter(start=0, increment=1)
start -= increment
lambda do
start += increment
end
end
class CounterTest < Test::Unit::TestCase
def test_counter
result = counter(0,2)
assert_equal 0, result.call
assert_equal 2, result.call
assert_equal 4, result.call
end
def test_another_one
another_one = counter(10, 3)
assert_equal 10, another_one.call
assert_equal 13, another_one.call
assert_equal 16, another_one.call
end
end
# 2 tests, 6 assertions, 0 failures, 0 errorsBlocks, closures, and define_method
class Example
def self.create_multiplier(name, factor)
define_method(name) do |arg|
arg * factor
end
end
end
Example.create_multiplier(:double, 2)
Example.create_multiplier(:triple, 3)
eg = Example.new
puts eg.double(33) # 66
puts eg.triple(33) # 99Ruby Internals
Now we are going lower level than we want to…Looking at the source of Ruby:
- Main interpreter in top-level source
- Ruby libraries in lib/
- C libraries in ext/
- Architecture-sepcific stuff in bcc32/, win32/, wince/ and 68k/
Most important files parse.y and eval.c
If you install ruby like I listed at the start of this entry you have all the source files, I have mine under /Users/daniel/Downloads/ruby-1.9.1-p0. Opening in textmate it looks as follows

ParseTree shows the nodes
sudo gem install ParseTree
Successfully installed RubyInline-3.8.1
Successfully installed sexp_processor-3.0.1
Successfully installed ParseTree-3.0.3
require 'rubygems'
require 'parse_tree'
class Daniel
def talk
"salut"
end
end
p = ParseTree.new
p.parse_tree(Daniel).to_yaml
---
- - :class
- :Daniel
- - :const
- :Object
- - :defn
- :talk
- - :scope
- - :block
- - :args
- - :str
- salut
=> nil- Note, doesn’t work i 1.9
- In 1.9 can call the interpreter
- Also ripper library, gives the output of the parse.
parse.y
Chad now explain how the parser works. In the parser.y you’ll find exactly how the parsing works. It’s over 10000 lines for Ruby 1.9
if_tail : opt_else
| keyword_elsif expr_value then
compstmt
if_tail
{
/*%%%*/
$$ = NEW_IF(cond($2), $4, $5);
fixpos($$, $2);
/*%
$$ = dispatch3(elsif, $2, $4, escape_Qundef($5));
%*/
}
;
expr_value : expr
{
/*%%%*/
value_expr($1);
$$ = $1;
if (!$$) $$ = NEW_NIL();
/*%
$$ = $1;
%*/
}
;Naming Conventions
- NODE_XX type of a node
- rb_cXxxx class object for Ruby clas Xxx
- rb_mXxxx module object for Ruby module xxxx
- rb_xxx C-implementation of a Ruby method
- Qxxx immediate value of Qnil
- RXXXXXX
Object Representation
Look in ruby.h for definition
To find Classes and Modules you can search in the c files:
$ grep String *.c | grep rb_define_class
string.c: rb_cString = rb_define_class("String", rb_cObject);If we look in string.c we see how the methods of the String Ruby class are defined in c.
void
Init_String(void)
{
#undef rb_intern
#define rb_intern(str) rb_intern_const(str)
rb_cString = rb_define_class("String", rb_cObject);
rb_include_module(rb_cString, rb_mComparable);
rb_define_alloc_func(rb_cString, str_alloc);
rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1);
rb_define_method(rb_cString, "initialize", rb_str_init, -1);
rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1);
rb_define_method(rb_cString, "<=>", rb_str_cmp_m, 1);
rb_define_method(rb_cString, "==", rb_str_equal, 1);
rb_define_method(rb_cString, "eql?", rb_str_eql, 1);
rb_define_method(rb_cString, "hash", rb_str_hash_m, 0);
rb_define_method(rb_cString, "casecmp", rb_str_casecmp, 1);
rb_define_method(rb_cString, "+", rb_str_plus, 1);
...Writing Extensions
Why?
- Interface to external libraries
- Performance
- Accessing internal
- Showing off
C-api is really nice.
Recipe is: create new directory, create source .c file. Create extconf.rb script and run it (requires ‘mkmf’). make. make install…It just all works.
Practice in your own user-local Ruby copy.
Example wrapping the Tidy library.
And onto lunch and exercise…Back at 1:15pm
- How many build-in classes define a to_i method?
- What’s the difference between Symbol#to_i and Symbol#to_int?
Ruby 1.9
- Encoding support and multinationalization
- Method Calling
- Blocks, Procs, and Iterators
- Regular Expressions
- Fibers
- Lots more…
- Performance
The 1.9 rdoc can be found here
# default argument values not only at end of argument list.
def lunch(spam=false, how_much)
spam ? puts("no thanks") : puts(how_much)
end
lunch(2)
lunch(true, 2)
#symbol to proc backed in
puts ['daffy', 'donald'].map(&:upcase)
# enumerator
e = [1,2,3].each
p e.next
p e.next
e.rewind
p e.next
# Enumerator halts loops
letters = ('a'..'e').to_enum
loop do
puts "STARTING: #{letters.next}" # .next Stops the loop at the end by raising StopIteration. Can call it your self.
end
puts "DONE"
# Stabby Procs
l = lambda{|x,y| "woo ugly #{x+y}"}.call(1,2) # 1.8
puts l
l =->x,y{"woo ugly #{x+y}"}.(1,2) # 1.9
puts l
# === is Margic
less_than_one_thousand =-> v{v<1000}
divisible_by_three =->v{v % 3 ==0}
case 909
when less_than_one_thousand
puts "#is Less than a thousand"
when divisible_by_three
puts "is Divisible by three"
end
puts 1005 === less_than_one_thousand
# Block paramters always local to block
n = 123
10.times do |n|
end
puts n
# Can declare block-local varaibles
number_processded = 12
pizza_shop =->(orders; number_processded) do # ; means use outside scope
puts "inside:#{number_processded}"
end
pizza_shop[["pepperoni", "anchovies"]]
puts "number_processded:#{number_processded}"
# Curry
adder =->(x,y){x+y}.curry
add_three = adder.(3)
# add_three.(1,2) ArgumentError: wrong number of arguments (3 for 2)
add_four = adder[4]
# Reglar Expression - (?<name>pattern)
if /(?<key>\w+)=(?<value>.*)/ =~ "ruby=cool and fun"
puts key
puts value
end
# Reglar Expression
# see http://pragdave.blogs.pragprog.com/pragdave/2008/10/fun-with-ruby-19-regular-expressions.html
# Fibers
fibs = Fiber.new do
n1 = n2 = 1
loop do
Fiber.yield n1
n1, n2 = n2, n1+n2
puts "n1:#{n1} n2:#{n2}"
end
end
10.times { print fibs.resume}
# Fibers - continuation
require 'continuation' # basis of continuation
require 'fiber' # symetrical coroutines
More Ruby 1.9 goodness
- RubyGems now backed in
- BasicObject
- Ripper: access to the parser
- Object#tap and Object#p
- Enumerable#cycle
- Hashes now retina their order
- Hash#select
- Minitest and minispec
# from http://pragdave.blogs.pragprog.com/pragdave/2008/04/babydoc.html
require 'ripper/filter'
require 'ripper'
# This class handles parser events, extracting
# comments and attaching them to class definitions
class BabyRDoc < Ripper::Filter
def initialize(*)
super
reset_state
end
def on_default(event, token, output)
reset_state
output
end
def on_sp(token, output) output end
alias on_nil on_sp
def on_comment(comment, output)
@comment << comment.sub(/^\s*#\s*/, " ")
output
end
def on_kw(name, output)
@expecting_class_name = (name == 'class')
output
end
def on_const(name, output)
if @expecting_class_name
output << "#{name}:\n"
output << @comment
end
reset_state
output
end
private
def reset_state
@comment = ""
@expecting_class_name = false
end
end
BabyRDoc.new(File.read(__FILE__)).parse(STDOUT)Performance
It’s really faster. Chad is showing some benchmarks, but experiencing makes you believe.
How to use 1.9 without blowing up your production version?
- Download or checkout source
- autoconf (if checked out)
- ./configure
- make
- make test
- sudo make install
Install’s it in /usr/local/ruby.
Now you may want to prefer installing it in it’s own directory. Then do
- ./configure —prefix=/usr/local/ruby19
- export PATH=/usr/local/ruby1.9/bin/:$PATH
When you install gems it install them in the ruby version you use to run the gem install.
require 'rbconfig' puts RbConfig::CONFIG
But wait…you can make it better. Dave installs it in it’s home folder.
- ./configure —prefix=/Users/dave/ruby19
Design in a Dynamic Language
- Can write in Java style, but it’s better to write using ruby.
- Inheritance (is-a) .vs. Composition (has-a)
- Inheritance is the work of the devil
- Inheritance the exception, composition the norm
Composition
require 'forwardable'
class Queue
extend Forwardable
def initialize
@q = [ ] # prepare delegate object
end
# setup prefered interface, enq() and deq()...
def_delegator :@q, :push, :enq
def_delegator :@q, :shift, :deq
# support some general Array methods that fit Queues well
def_delegators :@q, :clear, :first, :push, :shift, :size
end