dynamic languages, for software craftmanship group

Post on 26-Jan-2015

761 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Reuven Lerner's talk about dynamic programming languages in general, and about Ruby in particular. Why would you want to use a dynamic language? What can you do with one that isn't possible (or easy) with a static language?

TRANSCRIPT

Dynamic languagesReuven M. Lerner • reuven@lerner.co.ilSoftware 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

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"}]

Integer

12

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"}]

Integer String

12

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"}]

Integer String Array

12

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"}]

Integer String Array

Hash

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 counterdomains = 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 counterdomains = 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 counterdomains = 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 counterdomains = 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 counterdomains = 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 counterdomains = 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 counterdomains = 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 classesclass Person

end

=> nil

p = Person.new

=> #<Person:0x00000102105740>

p.class

=> Person

p.class.ancestors

=> [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject]

20

Defining classesclass Person

end

=> nil

p = Person.new

=> #<Person:0x00000102105740>

p.class

=> Person

p.class.ancestors

=> [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject]

20

Defining classesclass Person

end

=> nil

p = Person.new

=> #<Person:0x00000102105740>

p.class

=> Person

p.class.ancestors

=> [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject]

20

Defining classesclass Person

end

=> nil

p = Person.new

=> #<Person:0x00000102105740>

p.class

=> Person

p.class.ancestors

=> [Person, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel, BasicObject]

20

Defining classesclass 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” methodsclass Person

def blah

"blah"

end

end

=> nil

p = Person.new

p.class.instance_methods - p.class.superclass.instance_methods

=> [:blah]

23

“Local” methodsclass Person

def blah

"blah"

end

end

=> nil

p = Person.new

p.class.instance_methods - p.class.superclass.instance_methods

=> [:blah]

23

“Local” methodsclass Person

def blah

"blah"

end

end

=> nil

p = Person.new

p.class.instance_methods - p.class.superclass.instance_methods

=> [:blah]

23

“Local” methodsclass 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 findersReading.find_all_by_longitude_and_device_id(0, 3)

=> [#<Reading id: 46, longitude: #<BigDecimal:24439a8,'0.0',9(18)>, latitude: ... ]

Reading.instance_methods.grep(/long/)

=> []

32

Dynamic findersReading.find_all_by_longitude_and_device_id(0, 3)

=> [#<Reading id: 46, longitude: #<BigDecimal:24439a8,'0.0',9(18)>, latitude: ... ]

Reading.instance_methods.grep(/long/)

=> []

32

Dynamic findersReading.find_all_by_longitude_and_device_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

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Exampleh = {a:1, b:2}

=> {:a=>1, :b=>2}

h[:a]

=> 1

h.a

=> 1

h['c'] = 3

=> 3

h.c

=> nil

h[:d] = 4

=> 4

h.d

=> 4

34

Hash method accessclass 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 accessclass 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 accessclass 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 accessclass 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 accessclass 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 accessclass 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 includesmodule MyStuff

def self.included(klass)

klass.extend(ClassMethods)

end

module ClassMethods

def foo; end

end

end

37

Module includesmodule MyStuff

def self.included(klass)

klass.extend(ClassMethods)

end

module ClassMethods

def foo; end

end

end

37

Module includesmodule MyStuff

def self.included(klass)

klass.extend(ClassMethods)

end

module ClassMethods

def foo; end

end

end

37

Module includesmodule 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

top related