Dynamic languages
Reuven M. Lerner • reuven@lerner.co.il
Software Craftsmanship Group, Tel Aviv
          March 22nd, 2011




                                         1
Who am I?

• Web developer, software architect,
  consultant, trainer
• Linux Journal columnist since 1996
• Mostly Ruby on Rails + PostgreSQL, but
  also Python, PHP, jQuery, and lots more...



                                               2
Language wars!

• Static vs. dynamic languages
• I’m here representing the good guys
 • (Just kidding. Mostly.)
• Not just a different language — a different
  mindset and set of expectations



                                                3
What is a dynamic
       language?
• Dynamic typing
• Usually interpreted (or JIT compiled)
 • Interactive shell for experimenting
• Closures (anonymous code blocks)
• Flexible, “living” object model
• Result: Short, powerful, reusable code
                                           4
Who is in charge?

• Static language: The language is in charge,
  and it’s for your own good!
• Dynamic language: The programmer is in
  charge, and changes the language to suit his
  or her needs




                                                 5
6
Examples

• Lisp
• Smalltalk
• Python
• Ruby
• JavaScript

                          7
Examples

• Lisp
• Smalltalk
• Python
• Ruby
• JavaScript

                          7
Values, not variables,
     have types
x = 5
 => 5
x.class
 => Fixnum
x = [1,2,3]
 => [1, 2, 3]
x.class
 => Array


                         8
Values, not variables,
     have types
x = 5
 => 5
x.class
 => Fixnum
x = [1,2,3]
 => [1, 2, 3]
x.class
 => Array


                         8
Values, not variables,
     have types
x = 5
 => 5
x.class
 => Fixnum
x = [1,2,3]
 => [1, 2, 3]
x.class
 => Array


                         8
Values, not variables,
     have types
x = 5
 => 5
x.class
 => Fixnum
x = [1,2,3]
 => [1, 2, 3]
x.class
 => Array


                         8
Values, not variables,
     have types
x = 5
 => 5
x.class
 => Fixnum
x = [1,2,3]
 => [1, 2, 3]
x.class
 => Array


                         8
Less code!

• No need for variable declarations
• No function parameter declarations
• No function return-type declarations


                                         9
Ahhhh!

• Are you serious?
• Can real software be developed without a
  compiler and type checking?
• How can you possibly work this way?


                                             10
Ahhhh!

• Are you serious?
• Can real software be developed without a
  compiler and type checking?
• How can you possibly work this way?
 • Answer:Very well, thank you.

                                             10
11
Flexible collections
a = [1, 2, 'three', [4, 5, 6]]
 => [1, 2, "three", [4, 5, 6]]
a.length
 => 4
a << {first_name:'Reuven',
last_name:'Lerner'}
 => [1, 2, "three", [4, 5, 6],
{:first_name=>"Reuven",
:last_name=>"Lerner"}]



                                 12
Flexible collections
   Integer
a = [1, 2, 'three', [4, 5, 6]]
 => [1, 2, "three", [4, 5, 6]]
a.length
 => 4
a << {first_name:'Reuven',
last_name:'Lerner'}
 => [1, 2, "three", [4, 5, 6],
{:first_name=>"Reuven",
:last_name=>"Lerner"}]



                                 12
Flexible collections
   Integer   String
a = [1, 2, 'three', [4, 5, 6]]
 => [1, 2, "three", [4, 5, 6]]
a.length
 => 4
a << {first_name:'Reuven',
last_name:'Lerner'}
 => [1, 2, "three", [4, 5, 6],
{:first_name=>"Reuven",
:last_name=>"Lerner"}]



                                 12
Flexible collections
   Integer   String   Array
a = [1, 2, 'three', [4, 5, 6]]
 => [1, 2, "three", [4, 5, 6]]
a.length
 => 4
a << {first_name:'Reuven',
last_name:'Lerner'}
 => [1, 2, "three", [4, 5, 6],
{:first_name=>"Reuven",
:last_name=>"Lerner"}]



                                 12
Flexible collections
   Integer   String   Array
a = [1, 2, 'three', [4, 5, 6]]
 => [1, 2, "three", [4, 5, 6]]
a.length
 => 4
a << {first_name:'Reuven',
last_name:'Lerner'}          Hash
 => [1, 2, "three", [4, 5, 6],
{:first_name=>"Reuven",
:last_name=>"Lerner"}]



                                    12
Collections

• Built-in collections (arrays, hashes) are good
  for most basic data structures
• Only create a class when you need to!
• Learning to use arrays and hashes is a key
  part of dynamic programming, and can save
  you lots of time



                                                   13
Functional tricks

• collect (map)
• detect (find)
• select (find_all)
• reject
• inject

                          14
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Sorted username list
File.readlines('/etc/passwd').
 reject {|line| line =~ /^#/}.
 map {|line| line.split(':').
 first}.
 sort



                                 15
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Domain counter
domains = Hash.new(0)
File.readlines(list_member_file).each do |line|
      email, domain = line.chomp.split('@')
      domains[domain.downcase] += 1
end

domains.sort_by {|d| -d[1] }.
  each {|d| puts "#{d[1]} #{d[0]}" }

domains.sort_by {|d| [-d[1], d[0]] }.
  each {|d| puts "#{d[1]} #{d[0]}" }




                                                  16
Total cost of order

• Total price of an order of books:
  total_price = books.
  inject(0) do |sum, b|
     sum + (b.price * b.quantity)
  end




                                      17
Real-time require
require 'jobs/a'
require 'jobs/b'
require 'jobs/c'

Dir["#{Rails.root}/app/jobs/
*.rb"].
 each { |file| require file }


                                18
Real-time require
require 'jobs/a'
require 'jobs/b'
require 'jobs/c'

Dir["#{Rails.root}/app/jobs/
*.rb"].
 each { |file| require file }


                                18
Real-time require
require 'jobs/a'
require 'jobs/b'
require 'jobs/c'

Dir["#{Rails.root}/app/jobs/
*.rb"].
 each { |file| require file }


                                18
Classes

• Classes allow you to abstract behavior
• Classes contain data and methods
• It’s easy and fast to create a class


                                           19
Defining classes
class Person
end
 => nil
p = Person.new
 => #<Person:0x00000102105740>
p.class
 => Person
p.class.ancestors
 => [Person, Object, Wirble::Shortcuts,
PP::ObjectMixin, Kernel, BasicObject]



                                          20
Defining classes
class Person
end
 => nil
p = Person.new
 => #<Person:0x00000102105740>
p.class
 => Person
p.class.ancestors
 => [Person, Object, Wirble::Shortcuts,
PP::ObjectMixin, Kernel, BasicObject]



                                          20
Defining classes
class Person
end
 => nil
p = Person.new
 => #<Person:0x00000102105740>
p.class
 => Person
p.class.ancestors
 => [Person, Object, Wirble::Shortcuts,
PP::ObjectMixin, Kernel, BasicObject]



                                          20
Defining classes
class Person
end
 => nil
p = Person.new
 => #<Person:0x00000102105740>
p.class
 => Person
p.class.ancestors
 => [Person, Object, Wirble::Shortcuts,
PP::ObjectMixin, Kernel, BasicObject]



                                          20
Defining classes
class Person
end
 => nil
p = Person.new
 => #<Person:0x00000102105740>
p.class
 => Person
p.class.ancestors
 => [Person, Object, Wirble::Shortcuts,
PP::ObjectMixin, Kernel, BasicObject]



                                          20
Reflection
> p.methods # or Person.instance_methods
 =>
[:po, :poc, :pretty_print, :pretty_print_cycle,
:pretty_print_instance_variables, :pretty_print_inspect,
:nil?, :, :, :!, :eql?, :hash, :=>, :class, :singleton_class,
:clone, :dup, :initialize_dup, :initialize_clone, :taint,
:tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze,
:frozen?, :to_s, :inspect, :methods, :singleton_methods,
:protected_methods, :private_methods, :public_methods,
:instance_variables, :instance_variable_get,
:instance_variable_set, :instance_variable_defined?,
:instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send,
:respond_to?, :respond_to_missing?, :extend, :display,
:method, :public_method, :define_singleton_method, :__id__,
:object_id, :to_enum, :enum_for, :pretty_inspect, :ri, :,
:equal?, :!, :!, :instance_eval, :instance_exec, :__send__]




                                                                21
Predicates
p.methods.grep(/?$/)
 =>
[:nil?, :eql?, :tainted?,
:untrusted?, :frozen?,
:instance_variable_defined?,
:instance_of?, :kind_of?, :is_a?,
:respond_to?, :respond_to_missing?,
:equal?]



                                      22
“Local” methods
class Person
  def blah
    "blah"
    end
  end
 => nil
p = Person.new
p.class.instance_methods -
p.class.superclass.instance_methods
 => [:blah]



                                      23
“Local” methods
class Person
  def blah
    "blah"
    end
  end
 => nil
p = Person.new
p.class.instance_methods -
p.class.superclass.instance_methods
 => [:blah]



                                      23
“Local” methods
class Person
  def blah
    "blah"
    end
  end
 => nil
p = Person.new
p.class.instance_methods -
p.class.superclass.instance_methods
 => [:blah]



                                      23
“Local” methods
class Person
  def blah
    "blah"
    end
  end
 => nil
p = Person.new
p.class.instance_methods -
p.class.superclass.instance_methods
 => [:blah]



                                      23
Calling methods

a.length
 => 5
a.send(:length)
 => 5
a.send("length".to_sym)
 => 5




                          24
Calling methods

a.length
 => 5
a.send(:length)
 => 5
a.send("length".to_sym)
 => 5




                          24
Calling methods

a.length
 => 5
a.send(:length)
 => 5
a.send("length".to_sym)
 => 5




                          24
Calling methods

a.length
 => 5
a.send(:length)
 => 5
a.send("length".to_sym)
 => 5




                          24
Preferences become
    method calls
def bid_or_ask
    is_ask? ? :ask : :bid
  end


trade_amount = net_basis *
(end_rate.send(bid_or_ask) -
start_rate.send(bid_or_ask))



                               25
Preferences become
    method calls
def bid_or_ask
    is_ask? ? :ask : :bid
  end


trade_amount = net_basis *
(end_rate.send(bid_or_ask) -
start_rate.send(bid_or_ask))



                               25
Preferences become
    method calls
def bid_or_ask
    is_ask? ? :ask : :bid
  end


trade_amount = net_basis *
(end_rate.send(bid_or_ask) -
start_rate.send(bid_or_ask))



                               25
Define similar methods
['preview', 'applet', 'info',
'procedures', 'discuss', 'files',
'history', 'tags', 'family', 'upload',
'permissions'].each do |tab_name|
  define_method(
      "browse_#{tab_name}_tab".to_sym ) do
        render :layout => 'browse_tab'
  end
end



                                             26
Define similar methods
['preview', 'applet', 'info',
'procedures', 'discuss', 'files',
'history', 'tags', 'family', 'upload',
'permissions'].each do |tab_name|
  define_method(
      "browse_#{tab_name}_tab".to_sym ) do
        render :layout => 'browse_tab'
  end
end



                                             26
Define similar methods
['preview', 'applet', 'info',
'procedures', 'discuss', 'files',
'history', 'tags', 'family', 'upload',
'permissions'].each do |tab_name|
  define_method(
      "browse_#{tab_name}_tab".to_sym ) do
        render :layout => 'browse_tab'
  end
end



                                             26
Define similar methods
['preview', 'applet', 'info',
'procedures', 'discuss', 'files',
'history', 'tags', 'family', 'upload',
'permissions'].each do |tab_name|
  define_method(
      "browse_#{tab_name}_tab".to_sym ) do
        render :layout => 'browse_tab'
  end
end



                                             26
Define similar methods
['preview', 'applet', 'info',
'procedures', 'discuss', 'files',
'history', 'tags', 'family', 'upload',
'permissions'].each do |tab_name|
  define_method(
      "browse_#{tab_name}_tab".to_sym ) do
        render :layout => 'browse_tab'
  end
end



                                             26
Searching by result
'a'.what?('A')
"a".upcase == "A"
"a".capitalize == "A"
"a".swapcase == "A"
"a".upcase! == "A"
"a".capitalize! == "A"
"a".swapcase! == "A"
[:upcase, :capitalize, :swapcase,
:upcase!, :capitalize!, :swapcase!]


                                      27
Searching by result
'a'.what?('A')
"a".upcase == "A"
"a".capitalize == "A"
"a".swapcase == "A"
"a".upcase! == "A"
"a".capitalize! == "A"
"a".swapcase! == "A"
[:upcase, :capitalize, :swapcase,
:upcase!, :capitalize!, :swapcase!]


                                      27
Searching by result
'a'.what?('A')
"a".upcase == "A"
"a".capitalize == "A"
"a".swapcase == "A"
"a".upcase! == "A"
"a".capitalize! == "A"
"a".swapcase! == "A"
[:upcase, :capitalize, :swapcase,
:upcase!, :capitalize!, :swapcase!]


                                      27
Searching by result
'a'.what?('A')
"a".upcase == "A"
"a".capitalize == "A"
"a".swapcase == "A"
"a".upcase! == "A"
"a".capitalize! == "A"
"a".swapcase! == "A"
[:upcase, :capitalize, :swapcase,
:upcase!, :capitalize!, :swapcase!]


                                      27
Monkey patching!
class Fixnum
  def *(other)
      self + other
  end
end


6*5
 => 11


                         28
Monkey patching!
class Fixnum
  def *(other)
      self + other
  end
end


6*5
 => 11


                         28
Monkey patching!
class Fixnum
  def *(other)
      self + other
  end
end


6*5
 => 11


                         28
Example: “Whiny nils”

• Try to invoke “id” on nil, and Rails will
  complain
  @y.id
  RuntimeError: Called id for nil,
  which would mistakenly be 4 -- if
  you really wanted the id of nil,
  use object_id



                                              29
Example: RSpec
it "should have a unique name" do
  c1 = Currency.new(@valid_attributes)
  c1.save!
  c2 =
      Currency.new(
        :name => @valid_attributes[:name],
        :abbreviation => 'XYZ')
  c1.should be_valid
  c2.should_not be_valid
end




                                             30
Example: RSpec
it "should have a unique name" do
  c1 = Currency.new(@valid_attributes)
  c1.save!
  c2 =
      Currency.new(
        :name => @valid_attributes[:name],
        :abbreviation => 'XYZ')
  c1.should be_valid
  c2.should_not be_valid
end




                                             30
Example: RSpec
it "should have a unique name" do
  c1 = Currency.new(@valid_attributes)
  c1.save!
  c2 =
      Currency.new(
        :name => @valid_attributes[:name],
        :abbreviation => 'XYZ')
  c1.should be_valid
  c2.should_not be_valid
end




                                             30
method_missing


• If no method exists, then method_missing
  is invoked, passing the method name, args




                                              31
Dynamic finders
Reading.find_all_by_longitude_and_d
evice_id(0, 3)
 => [#<Reading id: 46, longitude:
#<BigDecimal:24439a8,'0.0',9(18)>,
latitude: ... ]


Reading.instance_methods.grep(/
long/)
 => []


                                      32
Dynamic finders
Reading.find_all_by_longitude_and_d
evice_id(0, 3)
 => [#<Reading id: 46, longitude:
#<BigDecimal:24439a8,'0.0',9(18)>,
latitude: ... ]


Reading.instance_methods.grep(/
long/)
 => []


                                      32
Dynamic finders
Reading.find_all_by_longitude_and_d
evice_id(0, 3)
 => [#<Reading id: 46, longitude:
#<BigDecimal:24439a8,'0.0',9(18)>,
latitude: ... ]


Reading.instance_methods.grep(/
long/)
 => []


                                      32
Hash methods as keys
class Hash
  def method_missing(name, *args)
      if has_key?(name)
        self[name]
      end
  end
end



                                    33
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Example
h = {a:1, b:2}        => 3
=> {:a=>1, :b=>2}     h.c
h[:a]                 => nil
=> 1                  h[:d] = 4
h.a                   => 4
=> 1                  h.d
h['c'] = 3            => 4


                                  34
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
Hash method access
class Hash
  def method_missing(method_name, *arguments)
      if has_key?(method_name)
        self[method_name]
      elsif has_key?(method_name.to_s)
        self[method_name.to_s]
      else
        nil
      end
  end
end




                                                35
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0"){
  xml.channel{
    xml.title("Modeling Commons updates")
    xml.link("http://modelingcommons.org/")
    xml.description("NetLogo Modeling Commons")
    xml.language('en-us')
      for update in @updates
        xml.item do
          xml.title(post.title)
          xml.description(post.html_content)
          xml.author("Your Name Here")
          xml.pubDate(post.created_on.strftime("%a,
%d %b %Y %H:%M:%S %z"))
          xml.link(post.link)
          xml.guid(post.link)
        end
      end
  }
}

                                                      36
Module includes
module MyStuff
      def self.included(klass)
        klass.extend(ClassMethods)
      end
      module ClassMethods
        def foo; end
      end
end


                                     37
Module includes
module MyStuff
      def self.included(klass)
        klass.extend(ClassMethods)
      end
      module ClassMethods
        def foo; end
      end
end


                                     37
Module includes
module MyStuff
      def self.included(klass)
        klass.extend(ClassMethods)
      end
      module ClassMethods
        def foo; end
      end
end


                                     37
Module includes
module MyStuff
      def self.included(klass)
        klass.extend(ClassMethods)
      end
      module ClassMethods
        def foo; end
      end
end


                                     37
Thanks!
     (Any questions?)

• Call me: 054-496-8405
• E-mail me: reuven@lerner.co.il
• Interrupt me: reuvenlerner (Skype/AIM)


                                           38

Dynamic languages, for software craftmanship group

  • 1.
    Dynamic languages Reuven M.Lerner • reuven@lerner.co.il Software Craftsmanship Group, Tel Aviv March 22nd, 2011 1
  • 2.
    Who am I? •Web developer, software architect, consultant, trainer • Linux Journal columnist since 1996 • Mostly Ruby on Rails + PostgreSQL, but also Python, PHP, jQuery, and lots more... 2
  • 3.
    Language wars! • Staticvs. dynamic languages • I’m here representing the good guys • (Just kidding. Mostly.) • Not just a different language — a different mindset and set of expectations 3
  • 4.
    What is adynamic language? • Dynamic typing • Usually interpreted (or JIT compiled) • Interactive shell for experimenting • Closures (anonymous code blocks) • Flexible, “living” object model • Result: Short, powerful, reusable code 4
  • 5.
    Who is incharge? • Static language: The language is in charge, and it’s for your own good! • Dynamic language: The programmer is in charge, and changes the language to suit his or her needs 5
  • 6.
  • 7.
    Examples • Lisp • Smalltalk •Python • Ruby • JavaScript 7
  • 8.
    Examples • Lisp • Smalltalk •Python • Ruby • JavaScript 7
  • 9.
    Values, not variables, have types x = 5 => 5 x.class => Fixnum x = [1,2,3] => [1, 2, 3] x.class => Array 8
  • 10.
    Values, not variables, have types x = 5 => 5 x.class => Fixnum x = [1,2,3] => [1, 2, 3] x.class => Array 8
  • 11.
    Values, not variables, have types x = 5 => 5 x.class => Fixnum x = [1,2,3] => [1, 2, 3] x.class => Array 8
  • 12.
    Values, not variables, have types x = 5 => 5 x.class => Fixnum x = [1,2,3] => [1, 2, 3] x.class => Array 8
  • 13.
    Values, not variables, have types x = 5 => 5 x.class => Fixnum x = [1,2,3] => [1, 2, 3] x.class => Array 8
  • 14.
    Less code! • Noneed for variable declarations • No function parameter declarations • No function return-type declarations 9
  • 15.
    Ahhhh! • Are youserious? • Can real software be developed without a compiler and type checking? • How can you possibly work this way? 10
  • 16.
    Ahhhh! • Are youserious? • Can real software be developed without a compiler and type checking? • How can you possibly work this way? • Answer:Very well, thank you. 10
  • 17.
  • 18.
    Flexible collections a =[1, 2, 'three', [4, 5, 6]] => [1, 2, "three", [4, 5, 6]] a.length => 4 a << {first_name:'Reuven', last_name:'Lerner'} => [1, 2, "three", [4, 5, 6], {:first_name=>"Reuven", :last_name=>"Lerner"}] 12
  • 19.
    Flexible collections Integer a = [1, 2, 'three', [4, 5, 6]] => [1, 2, "three", [4, 5, 6]] a.length => 4 a << {first_name:'Reuven', last_name:'Lerner'} => [1, 2, "three", [4, 5, 6], {:first_name=>"Reuven", :last_name=>"Lerner"}] 12
  • 20.
    Flexible collections Integer String a = [1, 2, 'three', [4, 5, 6]] => [1, 2, "three", [4, 5, 6]] a.length => 4 a << {first_name:'Reuven', last_name:'Lerner'} => [1, 2, "three", [4, 5, 6], {:first_name=>"Reuven", :last_name=>"Lerner"}] 12
  • 21.
    Flexible collections Integer String Array a = [1, 2, 'three', [4, 5, 6]] => [1, 2, "three", [4, 5, 6]] a.length => 4 a << {first_name:'Reuven', last_name:'Lerner'} => [1, 2, "three", [4, 5, 6], {:first_name=>"Reuven", :last_name=>"Lerner"}] 12
  • 22.
    Flexible collections Integer String Array a = [1, 2, 'three', [4, 5, 6]] => [1, 2, "three", [4, 5, 6]] a.length => 4 a << {first_name:'Reuven', last_name:'Lerner'} Hash => [1, 2, "three", [4, 5, 6], {:first_name=>"Reuven", :last_name=>"Lerner"}] 12
  • 23.
    Collections • Built-in collections(arrays, hashes) are good for most basic data structures • Only create a class when you need to! • Learning to use arrays and hashes is a key part of dynamic programming, and can save you lots of time 13
  • 24.
    Functional tricks • collect(map) • detect (find) • select (find_all) • reject • inject 14
  • 25.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 26.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 27.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 28.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 29.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 30.
    Sorted username list File.readlines('/etc/passwd'). reject {|line| line =~ /^#/}. map {|line| line.split(':'). first}. sort 15
  • 31.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 32.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 33.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 34.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 35.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 36.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 37.
    Domain counter domains =Hash.new(0) File.readlines(list_member_file).each do |line| email, domain = line.chomp.split('@') domains[domain.downcase] += 1 end domains.sort_by {|d| -d[1] }. each {|d| puts "#{d[1]} #{d[0]}" } domains.sort_by {|d| [-d[1], d[0]] }. each {|d| puts "#{d[1]} #{d[0]}" } 16
  • 38.
    Total cost oforder • Total price of an order of books: total_price = books. inject(0) do |sum, b| sum + (b.price * b.quantity) end 17
  • 39.
    Real-time require require 'jobs/a' require'jobs/b' require 'jobs/c' Dir["#{Rails.root}/app/jobs/ *.rb"]. each { |file| require file } 18
  • 40.
    Real-time require require 'jobs/a' require'jobs/b' require 'jobs/c' Dir["#{Rails.root}/app/jobs/ *.rb"]. each { |file| require file } 18
  • 41.
    Real-time require require 'jobs/a' require'jobs/b' require 'jobs/c' Dir["#{Rails.root}/app/jobs/ *.rb"]. each { |file| require file } 18
  • 42.
    Classes • Classes allowyou to abstract behavior • Classes contain data and methods • It’s easy and fast to create a class 19
  • 43.
    Defining classes class Person end => nil p = Person.new => #<Person:0x00000102105740> p.class => Person p.class.ancestors => [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject] 20
  • 44.
    Defining classes class Person end => nil p = Person.new => #<Person:0x00000102105740> p.class => Person p.class.ancestors => [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject] 20
  • 45.
    Defining classes class Person end => nil p = Person.new => #<Person:0x00000102105740> p.class => Person p.class.ancestors => [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject] 20
  • 46.
    Defining classes class Person end => nil p = Person.new => #<Person:0x00000102105740> p.class => Person p.class.ancestors => [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject] 20
  • 47.
    Defining classes class Person end => nil p = Person.new => #<Person:0x00000102105740> p.class => Person p.class.ancestors => [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject] 20
  • 48.
    Reflection > p.methods #or Person.instance_methods => [:po, :poc, :pretty_print, :pretty_print_cycle, :pretty_print_instance_variables, :pretty_print_inspect, :nil?, :, :, :!, :eql?, :hash, :=>, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :__id__, :object_id, :to_enum, :enum_for, :pretty_inspect, :ri, :, :equal?, :!, :!, :instance_eval, :instance_exec, :__send__] 21
  • 49.
    Predicates p.methods.grep(/?$/) => [:nil?, :eql?,:tainted?, :untrusted?, :frozen?, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :respond_to?, :respond_to_missing?, :equal?] 22
  • 50.
    “Local” methods class Person def blah "blah" end end => nil p = Person.new p.class.instance_methods - p.class.superclass.instance_methods => [:blah] 23
  • 51.
    “Local” methods class Person def blah "blah" end end => nil p = Person.new p.class.instance_methods - p.class.superclass.instance_methods => [:blah] 23
  • 52.
    “Local” methods class Person def blah "blah" end end => nil p = Person.new p.class.instance_methods - p.class.superclass.instance_methods => [:blah] 23
  • 53.
    “Local” methods class Person def blah "blah" end end => nil p = Person.new p.class.instance_methods - p.class.superclass.instance_methods => [:blah] 23
  • 54.
    Calling methods a.length =>5 a.send(:length) => 5 a.send("length".to_sym) => 5 24
  • 55.
    Calling methods a.length =>5 a.send(:length) => 5 a.send("length".to_sym) => 5 24
  • 56.
    Calling methods a.length =>5 a.send(:length) => 5 a.send("length".to_sym) => 5 24
  • 57.
    Calling methods a.length =>5 a.send(:length) => 5 a.send("length".to_sym) => 5 24
  • 58.
    Preferences become method calls def bid_or_ask is_ask? ? :ask : :bid end trade_amount = net_basis * (end_rate.send(bid_or_ask) - start_rate.send(bid_or_ask)) 25
  • 59.
    Preferences become method calls def bid_or_ask is_ask? ? :ask : :bid end trade_amount = net_basis * (end_rate.send(bid_or_ask) - start_rate.send(bid_or_ask)) 25
  • 60.
    Preferences become method calls def bid_or_ask is_ask? ? :ask : :bid end trade_amount = net_basis * (end_rate.send(bid_or_ask) - start_rate.send(bid_or_ask)) 25
  • 61.
    Define similar methods ['preview','applet', 'info', 'procedures', 'discuss', 'files', 'history', 'tags', 'family', 'upload', 'permissions'].each do |tab_name| define_method( "browse_#{tab_name}_tab".to_sym ) do render :layout => 'browse_tab' end end 26
  • 62.
    Define similar methods ['preview','applet', 'info', 'procedures', 'discuss', 'files', 'history', 'tags', 'family', 'upload', 'permissions'].each do |tab_name| define_method( "browse_#{tab_name}_tab".to_sym ) do render :layout => 'browse_tab' end end 26
  • 63.
    Define similar methods ['preview','applet', 'info', 'procedures', 'discuss', 'files', 'history', 'tags', 'family', 'upload', 'permissions'].each do |tab_name| define_method( "browse_#{tab_name}_tab".to_sym ) do render :layout => 'browse_tab' end end 26
  • 64.
    Define similar methods ['preview','applet', 'info', 'procedures', 'discuss', 'files', 'history', 'tags', 'family', 'upload', 'permissions'].each do |tab_name| define_method( "browse_#{tab_name}_tab".to_sym ) do render :layout => 'browse_tab' end end 26
  • 65.
    Define similar methods ['preview','applet', 'info', 'procedures', 'discuss', 'files', 'history', 'tags', 'family', 'upload', 'permissions'].each do |tab_name| define_method( "browse_#{tab_name}_tab".to_sym ) do render :layout => 'browse_tab' end end 26
  • 66.
    Searching by result 'a'.what?('A') "a".upcase== "A" "a".capitalize == "A" "a".swapcase == "A" "a".upcase! == "A" "a".capitalize! == "A" "a".swapcase! == "A" [:upcase, :capitalize, :swapcase, :upcase!, :capitalize!, :swapcase!] 27
  • 67.
    Searching by result 'a'.what?('A') "a".upcase== "A" "a".capitalize == "A" "a".swapcase == "A" "a".upcase! == "A" "a".capitalize! == "A" "a".swapcase! == "A" [:upcase, :capitalize, :swapcase, :upcase!, :capitalize!, :swapcase!] 27
  • 68.
    Searching by result 'a'.what?('A') "a".upcase== "A" "a".capitalize == "A" "a".swapcase == "A" "a".upcase! == "A" "a".capitalize! == "A" "a".swapcase! == "A" [:upcase, :capitalize, :swapcase, :upcase!, :capitalize!, :swapcase!] 27
  • 69.
    Searching by result 'a'.what?('A') "a".upcase== "A" "a".capitalize == "A" "a".swapcase == "A" "a".upcase! == "A" "a".capitalize! == "A" "a".swapcase! == "A" [:upcase, :capitalize, :swapcase, :upcase!, :capitalize!, :swapcase!] 27
  • 70.
    Monkey patching! class Fixnum def *(other) self + other end end 6*5 => 11 28
  • 71.
    Monkey patching! class Fixnum def *(other) self + other end end 6*5 => 11 28
  • 72.
    Monkey patching! class Fixnum def *(other) self + other end end 6*5 => 11 28
  • 73.
    Example: “Whiny nils” •Try to invoke “id” on nil, and Rails will complain @y.id RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id 29
  • 74.
    Example: RSpec it "shouldhave a unique name" do c1 = Currency.new(@valid_attributes) c1.save! c2 = Currency.new( :name => @valid_attributes[:name], :abbreviation => 'XYZ') c1.should be_valid c2.should_not be_valid end 30
  • 75.
    Example: RSpec it "shouldhave a unique name" do c1 = Currency.new(@valid_attributes) c1.save! c2 = Currency.new( :name => @valid_attributes[:name], :abbreviation => 'XYZ') c1.should be_valid c2.should_not be_valid end 30
  • 76.
    Example: RSpec it "shouldhave a unique name" do c1 = Currency.new(@valid_attributes) c1.save! c2 = Currency.new( :name => @valid_attributes[:name], :abbreviation => 'XYZ') c1.should be_valid c2.should_not be_valid end 30
  • 77.
    method_missing • If nomethod exists, then method_missing is invoked, passing the method name, args 31
  • 78.
    Dynamic finders Reading.find_all_by_longitude_and_d evice_id(0, 3) => [#<Reading id: 46, longitude: #<BigDecimal:24439a8,'0.0',9(18)>, latitude: ... ] Reading.instance_methods.grep(/ long/) => [] 32
  • 79.
    Dynamic finders Reading.find_all_by_longitude_and_d evice_id(0, 3) => [#<Reading id: 46, longitude: #<BigDecimal:24439a8,'0.0',9(18)>, latitude: ... ] Reading.instance_methods.grep(/ long/) => [] 32
  • 80.
    Dynamic finders Reading.find_all_by_longitude_and_d evice_id(0, 3) => [#<Reading id: 46, longitude: #<BigDecimal:24439a8,'0.0',9(18)>, latitude: ... ] Reading.instance_methods.grep(/ long/) => [] 32
  • 81.
    Hash methods askeys class Hash def method_missing(name, *args) if has_key?(name) self[name] end end end 33
  • 82.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 83.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 84.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 85.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 86.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 87.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 88.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 89.
    Example h = {a:1,b:2} => 3 => {:a=>1, :b=>2} h.c h[:a] => nil => 1 h[:d] = 4 h.a => 4 => 1 h.d h['c'] = 3 => 4 34
  • 90.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 91.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 92.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 93.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 94.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 95.
    Hash method access classHash def method_missing(method_name, *arguments) if has_key?(method_name) self[method_name] elsif has_key?(method_name.to_s) self[method_name.to_s] else nil end end end 35
  • 96.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 97.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 98.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 99.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 100.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 101.
    xml.instruct! :xml, :version=>"1.0" xml.rss(:version=>"2.0"){ xml.channel{ xml.title("Modeling Commons updates") xml.link("http://modelingcommons.org/") xml.description("NetLogo Modeling Commons") xml.language('en-us') for update in @updates xml.item do xml.title(post.title) xml.description(post.html_content) xml.author("Your Name Here") xml.pubDate(post.created_on.strftime("%a, %d %b %Y %H:%M:%S %z")) xml.link(post.link) xml.guid(post.link) end end } } 36
  • 102.
    Module includes module MyStuff def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def foo; end end end 37
  • 103.
    Module includes module MyStuff def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def foo; end end end 37
  • 104.
    Module includes module MyStuff def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def foo; end end end 37
  • 105.
    Module includes module MyStuff def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def foo; end end end 37
  • 106.
    Thanks! (Any questions?) • Call me: 054-496-8405 • E-mail me: reuven@lerner.co.il • Interrupt me: reuvenlerner (Skype/AIM) 38