Download Ruby - the power of dynamic programming

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Ruby - the power of
dynamic programming
Niclas Nilsson - http://niclasnilsson.se
Copyright 2006 Activa Sweden AB
About Niclas
Software developer and educator
Founder of the consultancy Activa,
http://www.activa.se
Blogs at http://niclasnilsson.se
Who are you?
Introduction
Focus on the dynamic part of Ruby
Java as a (more static) comparison
language
Objects everywhere
# Everything is an object.
# There are no 'primitive' types.
puts -12.abs
puts 42.zero?
puts 19.class
#
#
#
#
output:
12
false
Fixnum
Objects everywhere
# Everything is an object.
# There are no 'primitive' types.
3.times do
puts 'Hello!'
end
#
#
#
#
output:
Hello
Hello
Hello
Exactly everything is an
object
n = nil
n.class
n.equal?("foo")
n.nil?
n.capitalize!
#
#
#
#
=>
=>
=>
=>
NilClass
false
true
throws NoMethodError
Syntactic sugar
puts 1020
puts 100_000_000_000_000
A simple class in two
languages
// A Person class in Java
package se.activa.dynlang;
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public String toString() {
return "[" + name + ", " + age + "]";
}
class Person
attr :name
attr :age
def initialize(name, age)
@name = name
@age = age
end
def to_s
"[#{@name}, #{@age}]"
end
end
Containers
// List filtering in Java
package se.activa.dynlang;
import java.util.*;
public class FilterList {
public static void main(String[] args) {
String[] names = { "Adrian Smith", "Robert Johnson",
"Eric Clapton", "Steve Vai", "John Scofield",
"Eric Johnson", "Steve Cropper" };
List<String> guitarists = Arrays.asList(names);
List<String> steves = new ArrayList<String>();
for (Iterator<String> i = guitarists.iterator();
i.hasNext();) {
String name = i.next();
if (name.contains("Steve")) {
steves.add(name);
}
}
}
}
System.out.println(steves);
# List filtering in Ruby
guitarists = ["Adrian Smith", "Robert Johnson",
"Eric Clapton", "Steve Vai", "John Scofield",
"Eric Johnson", "Steve Cropper"]
steves = guitarists.select do |name|
name =~ /Steve/
end
puts steves
Type safety
Dynamic languages have no static compile
time type checks
number = "42";
# Runtime error:
sum = number + 3;
(Many dynamic language implementations
does actually not even have a compile step
at all)
Type safety in Java
package se.activa.dynlang;
public class TypeError {
public static void main(String[] args) {
String number = "42";
}
}
// Compile error:
// int sum = number + 3;
Java Collections <= ver 1.4
package se.activa.dynlang;
import java.util.*;
public class CastException {
public static void main(String[] args) {
Vector names = new Vector();
names.add("Eric Johnson");
names.add(new Integer(14));
names.add("Steve Cropper");
}
}
for (Iterator iter=names.iterator(); iter.hasNext();) {
String name = (String) iter.next();
name = name + " - guitar player";
}
Java >= ver 1.5
package se.activa.dynlang;
import java.util.*;
public class Generics {
public static void main(String[] args) {
Vector<String> names = new Vector<String>();
names.add("Eric Johnson");
// Compile error:
// names.add(new Integer(14));
names.add("Steve Cropper");
}
}
for (Iterator<String> iter = names.iterator();
iter.hasNext();) {
String name = iter.next();
name = name + " - guitar player";
}
A problem?
Less of a problem than expected
A lot of ClassCastExceptions before JDK
1.5?
Quite rare that code gets surprised
about content in a collection.
No Intellisense / Code Completions in
most editors
Back to reading the documentation
Solution?
TDD
Test-Driven Development
Unit tests and integration tests
Tests type usage
...and the code behavior
Non dynamic types
package se.activa.dynlang;
public class Bignum {
public static void main(String[] args) {
Integer i = 0;
System.out.println(i);
// 0
System.out.println(i.getClass()); // java.lang.Integer
i = 100000000;
System.out.println(i);
// 100000000
System.out.println(i.getClass()); // java.lang.Integer
}
}
i = i * i;
System.out.println(i);
// 1874919424
System.out.println(i.getClass()); // java.lang.Integer
Dynamic types
sum = 0
puts sum
puts sum.class
# 0
# Fixnum
sum = 100_000_000
puts sum
# 100000000
puts sum.class
# Fixnum
sum = sum * sum
puts sum
puts sum.class
# 10000000000000000
# Bignum
Passing arguments
Is it harder to know argument types?
Sometimes it is.
The documentation often makes it clear
by giving good names and/or
descriptions
Dynamic typing - arguments
Instead of overloading
not unusual for a method to accept
different types for one argument
Example Net::IMAP.copy(set, mailbox)
The set parameter is either
a number
an array of numbers
a Range object.
Interfaces
No 'normal' interfaces
Duck typing instead
Duck typing is what dynamic typing is
called in many languages.
'If it walks like a duck and quacks like a
duck, it must be a duck.'
Note! Not weak types
Simply checked in runtime instead of at
compile time
// In a language without duck typing
interface FlyingObject {
public void fly();
}
class Duck implements FlyingObject {
public void fly() {
System.out.println("I'm a duck and I'm flying");
}
}
class Airplane implements FlyingObject {
public void fly() {
System.out.println("I'm an airplane and I'm flying");
}
}
public class NoDuckTyping {
public static void main(String[] args) {
FlyingObject[] flyers = { new Duck(), new Airplane() };
for (int i = 0; i < flyers.length; i++) {
flyers[i].fly();
}
}
}
# Duck typing example
class Duck
def fly
puts "I'm a duck and I'm flying"
end
end
class Airplane
def fly
puts "I'm an airplane and I'm flying"
end
end
flyers = [ Duck.new(), Airplane.new() ]
flyers.each do |flyer|
flyer.fly
end
Duck typing
The question is not:
Do you confirm to this interface?
The question is:
Can you handle the messages I send
you?
Blocks
Blocks of code that can be passed to
method
Common in functional languages
Blocks and closures are (more or less)
synonyms
Blocks can access local variables in the
defining method
Block-like things in static
languages
C and C++ has function pointers
Java has anonymous inner classes
C# 2.0 has closures! (anonymous
methods)
// Without duck typing: An implementation of the Comparator
// interface used for sorting Person objetcs.
class PersonComparator implements Comparator<Person> {
public int compare(Person person1, Person person2) {
return person1.getAge() - person2.getAge();
}
}
public class SortingPersons {
public static void main(String[] args) {
Person james = new Person("James", 40);
Person karen = new Person("Karen", 45);
Person hanna = new Person("Hanna", 18);
List<Person> persons = new ArrayList<Person>();
persons.add(james);
persons.add(karen);
persons.add(hanna);
}
}
PersonComparator pc = new PersonComparator();
Collections.sort(persons, pc);
System.out.println(persons);
// Without duck typing - alternative solution:
// Sort the collection by creating an anonymous inner
// class that implements the Comparator interface
public class SortingPersonsAnonymous {
public static void main(String[] args) {
Person james = new Person("James", 40);
Person karen = new Person("Karen", 45);
Person hanna = new Person("Hanna", 18);
List<Person> persons = new ArrayList<Person>();
persons.add(james);
persons.add(karen);
persons.add(hanna);
Collections.sort(persons, new Comparator<Person>() {
public int compare(Person person1, Person person2) {
return person1.getAge() - person2.getAge();
}
});
}
}
System.out.println(persons);
# With block
james = Person.new("James", 40)
karen = Person.new("Karen", 45)
hanna = Person.new("Hanna", 18)
persons = [james, karen, hanna]
persons.sort! do |person1, person2|
person1.age - person2.age
end
puts persons
The closest thing in Java
public class PersonPrinter {
void print(Person person) {
System.out.println(person);
}
}
public class BlockExample {
public void printAllSeniors(List<Person> persons,
PersonPrinter printer) {
Iterator iter = persons.iterator();
while (iter.hasNext()) {
Person person = (Person) iter.next();
if (person.getAge() >= 65) {
printer.print(person);
}
}
}
}
public static void main(String[] args) {
// Creating and adding persons (removed from example)
BlockExample be = new BlockExample();
be.printAllSeniors(persons, new PersonPrinter());
}
public class BlockExample2 {
public void printAllSeniors(List<Person> persons,
PersonPrinter printer) {
Iterator iter = persons.iterator();
while (iter.hasNext()) {
Person person = (Person) iter.next();
if (person.getAge() >= 65) {
printer.print(person);
}
}
}
}
public static void main(String[] args) {
// Creating and adding persons (removed from example)
BlockExample be = new BlockExample();
be.printAllSeniors(persons, new PersonPrinter() {
void print(Person person) {
System.out.println("P: " + person);
}
});
}
Blocks examples
require 'Person'
def each_senior(persons, &block)
persons.each do |person|
yield person if person.age >= 65
end
end
harold = Person.new("Harold", 75)
james = Person.new("James", 40)
karen = Person.new("Karen", 45)
hanna = Person.new("Hanna", 18)
ruth = Person.new("Ruth", 65)
persons = [harold, james, karen, hanna, ruth]
each_senior(persons) do |senior|
puts senior
end
# output:
# [Harold, 75]
# [Ruth, 65]
require 'Person'
def each_senior(persons)
persons.each do |person|
yield person if person.age >= 65
end
end
harold = Person.new("Harold", 75)
james = Person.new("James", 40)
karen = Person.new("Karen", 45)
hanna = Person.new("Hanna", 18)
ruth = Person.new("Ruth", 65)
persons = [harold, james, karen, hanna, ruth]
each_senior(persons) do |senior|
puts senior
end
# output:
# [Harold, 75]
# [Ruth, 65]
require 'Person'
def each_senior(persons)
persons.each do |person|
yield person if person.age >= 65
end
end
harold = Person.new("Harold", 75)
james = Person.new("James", 40)
karen = Person.new("Karen", 45)
hanna = Person.new("Hanna", 18)
ruth = Person.new("Ruth", 65)
persons = [harold, james, karen, hanna, ruth]
each_senior(persons)
#
#
#
#
#
output:
LocalJumpError: no block given
method each_senior
in block_example2.rb at line 5
method each_senior
in block_example2.rb at line 4
at top level in block_example2.rb at line 21
require 'Person'
def each_senior(persons)
persons.each do |person|
yield person if person.age >= 65 if (block_given?)
end
end
harold = Person.new("Harold", 75)
james = Person.new("James", 40)
karen = Person.new("Karen", 45)
hanna = Person.new("Hanna", 18)
ruth = Person.new("Ruth", 65)
persons = [harold, james, karen, hanna, ruth]
each_senior(persons) do |senior|
puts senior
end
each_senior(persons)
# output:
# [Harold, 75]
# [Ruth, 65]
Using blocks for IO
# Passing a block to File.open automatically
# closes the file when block is done
File.open("primes.txt", "r") do |file|
file.each_line { |line| puts line }
end
Closures
Closures are blocks that have been bound
to local variables
Used to defer calculations
Used as arguments to higher order
functions
(functions that creates functions from
functions...)
# From the Ruby documentation
def gen_times(factor)
lambda {|n| n*factor }
end
times3 = gen_times(3)
times5 = gen_times(5)
puts times3.call(12)
puts times5.call(5)
puts times3.call(times5.call(4))
#=> 36
#=> 25
#=> 60
Mixins
module Logger
def log_info(message)
puts "INFO: #{Time.now} #{message}"
end
end
class User
include Logger
# ...
end
ph = User.new
ph.log_info("some message")
Extends
module Logger
def log_info(message)
puts "INFO: #{Time.now} #{message}"
end
end
class User
end
user = User.new
# user.log_info("some message")
# NoMethodError
user.extend(Logger)
user.log_info("another message")
Reflecton
Exists in many static languages, but not
all
C++ has very limited reflection capabilities
C has no reflection capabilities
Are you of this type?
puts 3.instance_of?(Fixnum)
puts 3.instance_of?(Numeric)
puts 3.kind_of?(Numeric)
#
#
#
#
output:
true
false
true
Do you respond to this?
package se.activa.dynlang;
import java.lang.reflect.*;
public class RespondTo {
public static void main(String[] args) {
Object o = "foobar";
}
}
try {
Method m = o.getClass().getMethod("length", null);
// Yes, it had a method called length (with 0 args)
}
catch (NoSuchMethodException e) {
// ...
}
Do you respond to this?
str = "foo"
puts str.respond_to?("upcase")
puts 3.respond_to?("upcase")
# output:
# true
# false
Sending messages in static
languages
1. Look up method in virtual table
2. Call the method
Sending messages in
dynamic languages
1. Send a message to an object
2. The object responds (or not) to the
message
'Sending messages'
package se.activa.dynlang;
import java.lang.reflect.*;
public class ReflectiveCalls {
public static void main(String[] args) {
String str = "foobar";
try {
Method m = str.getClass().getMethod("length", null);
Integer result = (Integer) m.invoke(str, null);
// ...
}
catch (NoSuchMethodException e) {
// ...
}
catch (InvocationTargetException e) {
// ...
}
catch (IllegalAccessException e) {
// ...
'Sending messages'
package se.activa.dynlang;
import java.lang.reflect.*;
public class ReflectiveCalls2 {
}
public static void main(String[] args) {
String str = "foobar";
try {
Method m = str.getClass().getMethod("length", null);
Integer result = (Integer) m.invoke(str, null);
// ...
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
Sending messages
str = "foobar"
puts str.send("size")
Method missing
class PersonList < Array
# ...
end
persons
persons
persons
persons
= PersonList.new
<< Person.new("John Smith", 75)
<< Person.new("Andy Taylor", 50)
<< Person.new("John Smith", 45)
puts persons.find_by_name("John Smith")
puts persons.find_by_age(50)
puts persons.find_by_occupation("programmer")
Output:
[John Smith, 75]
[John Smith, 45]
[Andy Taylor, 50]
class PersonList < Array
def method_missing(method, *args, &block)
# parse the method name
method_name = method.id2name
if method_name =~ /find_by_.*/
property = method_name.sub(/find_by_/, "")
end
matches = select do |person|
if person.respond_to?(property)
person.send(property) == args[0]
else
false
end
end
matches
end
end
Null Object implementation
class NullObject
def method_missing(method, *args, &block)
self
end
end
null_obj = NullObject.new
a = null_obj.do_something_very_interesting
b = null_obj.zzzzzzz
puts "a equals b" if a == b
Eval
execute a string as code
code is data, data is code
Eval
class Model
def self.finder(name)
eval %{
def #{name}
# ...
# ...
end
}
end
end
class User < Model
finder :find_by_name
finder :find_by_name_and_password
end
u = User.new
u.find_by_name
# f.find_by_address
# will not execute
Code generation on the fly!
In Java
BCEL (Byte Code Engineering Library)
and CGLIB (Code Generation Library)
Operated on binary level (.class-files)
In dynamic languages
eval
Recognized that pattern?
class User < Model
finder :find_by_name
finder :find_by_name_and_password
end
Person again
class Person
attr :name
attr :age
def initialize(name, age)
@name = name
@age = age
end
def to_s
"[#{@name}, #{@age}]"
end
end
Attributes revised
class Foo
attr :size, true
end
foo = Foo.new
foo.size = 2
puts "size is #{foo.size}"
Attributes explicit
class Foo
def size
@size
end
def size=(val)
@size = val
end
end
foo = Foo.new
foo.size = 2
puts "size is #{foo.size}"
Rolling you own
# Adding behavior to class Object
class Object
def attr_java_style(name)
name = name.id2name
eval %{
def get#{name.capitalize}
@#{name}
end
}
end
end
def set#{name.capitalize}(#{name})
@#{name} = #{name}
end
Rolling you own
# Using the new behavious in inherited
# from class Object
class Person
attr_java_style :name
attr_java_style :age
def initialize(name, age)
@name = name
@age = age
end
def to_s
"[#{@name}, #{@age}]"
end
end
person = Person.new("James", 42)
puts "#{person.getName} is #{person.getAge} years old."
No closed classes
Add methods
can also add methods to objects
not only to classes
Adding methods to objects
foo = "foo"
bar = "bar"
def foo.capitalized?
self[0, 1] == self[0, 1].to_s.upcase
end
puts foo.capitalized?
puts bar.capitalized?
# false
# NoMethodError
Aliasing
class Sheep
def initialize(name)
@name = name
end
def speak
puts "Baaaah!"
end
end
Aliasing
# Sometime later, someone wants to log when
# a sheep is speaking
class Sheep
alias_method :speak_original, :speak
def speak
puts "At #{Time.now} the sheep called #{@name} says"
speak_original()
end
end
mary = Sheep.new('Mary')
mary.speak()
# output:
# At Sat Aug 19 19:22:55 CEST 2006 the sheep called Mary says
# Baaaah!
Changing visibility
class ShoppingCart
def initialize
@total = 0
end
# A lots of methods excluded...
def total
@total - @total * discount_in_percent(@total)
end
private
def discount_in_percent
discount = case
when @total > 100 : 0.05
when @total > 500 : 0.10
end
end
end
Changing visibility
require "test/unit"
class ShoppingCart
public :discount_in_percent
end
class TestShoppingCart < Test::Unit::TestCase
def test_discount_in_percent
cart = ShoppingCart.new
# setup things to test
# Testing a private method!
cart.discount_in_percent
end
end
Questions?
Final words
That's all folks!