The slides for a lecture about the Ruby programming language. This language was given at FEUP, on a course called "Laboratories of Object-Oriented Programming".
3. Ruby
Created by Yukihiru “Matz” Matsumoto, in Japan
Perl + SmallTalk + Eiffel + Ada + Lisp = Ruby
“I wanted a scripting language that was more powerful than Perl, and more object-oriented than Python.” — matz
4. Ruby
Interpreted language, with many implementations: YARV , Rubinius , JRuby , etc
Functional, object-oriented, imperative and reflective
Dynamically typed, with automatic garbage collection, exception handling and
built-in unit testing
Optimized for programmer productivity and happiness
13. It's really object-oriented
In Ruby, all functions and most operators are methods of an object
In Python, for instance, some functions are procedural
Classes themselves are instances of Class
14. It's really object-oriented
Ruby has a permanent notion of the current object : self
self controls how Ruby accesses instance variables
All method calls are made on some object, called the receiver
If no receiver is specified, self is used — it is implicit!
15. It's really object-oriented
# self is "main", an instance of Object
class MyClass
# self is MyClass
def my_method
# self will depend on the receiver for this method
end
end
# self is "main" again, so my_object exists in the main scope
my_object = MyClass.new
# explicit receiver, so self will be my_object inside my_method
my_object.my_method
16. Arrays
Store indexed collections of objects accessible through an integer key
Can contain objects with different classes simultaneously
# array
a = [1, "second", 3.14]
a[2]
17. Hashes
Store indexed collections of objects accessible through a key which can be any
object
Slightly less efficient but much more flexible
# hash
h = {
"string" => [3,4,5],
2 => "everything can be a value!",
[1,2] => "everything can be a key!"
}
h[[1,2]]
18. Symbols
A significant name, generally a static variable
// java
static final int NORTH = 1;
// ... more code
move(NORTH);
# ruby
move(:north)
No need to predeclare these constants, they are unique
return true if direction == :north
19. Control Structures
# if
if score.between?(100, 199)
puts "You rock!"
elsif score < 50
puts "You suck!"
else
puts "Meh"
end
# while
while score < 100
puts "You're almost there! Try again..."
# ...
end
# more goodness with unless, until, case, for, etc
# and awesome shortcuts like statement modifiers
puts "You cheated!" if score >= 200
21. Blocks
A chuck of code enclosed between { } or do/end keywords
Similar to the concept of anonymous methods
Can take parameters
Can be passed to methods as arguments (at the end, like an extra parameter)
sum = 0
(1..5).each do |n| # same as [1,2,3,4,5]
sum += n
end
sum
# same thing with { }
sum = 0
(1..5).each { |n| sum += n }
sum
22. Blocks as Objects
Can be converted into objects
Can be stored in variables, pass them around, invoke them whenever you want
Great for implementing callbacks, dispatch tables, etc
class BlockAsObject
def store_block(&my_block)
@stored_block = my_block # converted to Proc
end
def use_block(parameter)
@stored_block.call(parameter)
end
end
foo = BlockAsObject.new
foo.store_block { |param| "The block was called with " + param }
foo.use_block("delay")
23. Blocks as Closures
They can use local variables from the surrounding scope
def powers_of_2_proc
value = 1
lambda { value += value }
end
powers_of_2 = powers_of_2_proc
powers_of_2.call # 2
powers_of_2.call # 4
powers_of_2.call # will return 8!
So, powers_of_2_proc returns a Proc that references value
When the block is called, value is out of scope
The block is still able to access it (and will be for the remaining life of this block)
24. Iterators
In many languages, collections implement methods to generate external iterator
objects
// C++
for (std::vector<int>::iterator i=list.begin(); i!=list.end(); i++) {
// code
}
25. Iterators
In many languages, collections implement methods to generate external iterator
objects
// C#
IEnumerator<int> i = list.GetEnumerator();
while (i.MoveNext()) {
// code
}
26. Iterators
In many languages, collections implement methods to generate external iterator
objects
// Java
Iterator i = list.iterator();
while (i.hasNext()) {
// code
}
27. Iterators
When coming from other languages, many people iterate collections like this:
# familiar?
for i in 0..2
number = array[i][0]
word = array[i][1]
puts "#{word}: #{number}"
end
28. Iterators
However, there's another approach:
# the "ruby way", with a lot less coupling
array.each do |word, number|
puts "#{word}: #{number}"
end
The “Ruby way” is different: an iterator is internal to the collection... it's just a
method that calls yield every time it generates a new value
29. Iterators
Ruby provides a lot of useful iterators: each, map , inject , etc
But you can build your own
def fib(max)
i1, i2 = 1, 1 # parallel assignment
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
result = ""
fib(1337) { |n| result += "#{n} " }
result
30. Iterators
Ruby's internal iterators aren't necessarily the best solution
What if you need the iterator to be an object?
What if you want to iterate multiple collections simultaneously?
31. Enumerators
When iterators aren't suitable, you can resort to enumerators
To put it simply, an enumerator is an external iterator
array = ["my", 1337, "array"]
enumerator = array.to_enum # same as "enumerator = array.each"
enumerator.next # returns "my" and moves to the next element
enumerator.next
32. Enumerators
Most internal iterators are can be used as enumerators
string = "le fu"
enumerator = string.each_char
enumerator.next # returns "l" and moves to the next char
enumerator.next # returns "e" and moves to the next char
33. Containers, containers, containers!
Blocks and iterators are core concepts of Ruby
With practice, you'll start building classes that iterate over their contents instead
of using the conventional looping constructs
It might seem complicated at first, but you'll start using these features naturally
Easy to read and maintain
35. Inheritance
class SuperClass
def hello_world
"Hello world!"
end
end
class SubClass < SuperClass
end
superclass = SuperClass.new
subclass = SubClass.new
"Hello world from the superclass: #{superclass.hello_world}n" +
"Hello world from the subclass : #{subclass.hello_world}"
36. Inheritance
Single inheritance, unlike C++
Top-level classes are subclasses of Object , which is a subclass of BasicObject
Classes have built-in functionality because they inherited it!
37. Modules
Modules provide a namespace, avoiding name collisions
module MyModule # a module has it all
MY_CONSTANT = "my constant" # constants
def my_method # methods
end
class MyClass # and classes
end
end
namespaced = MyModule::MyClass.new
Modules have another wonderful use: mixins
38. Mixins and Inheritance
Some OO languages support multiple inheritance (C++, Lisp, Python, etc)
This is very powerful, but can be troublesome because of inheritance ambiguity
Ruby offers a great compromise: the simplicity of single inheritance and the
power of multiple inheritance
39. Mixins
include is used to mix modules into classes or other modules
module ToBeIncluded
def foo
"bar"
end
end
class MyClass
include ToBeIncluded
end
object = MyClass.new
object.foo
40. Mixins
extend is used to add instance methods to a given object
module ToBeIncluded
def foo
"bar"
end
end
class MyClass
end
object = MyClass.new
object.extend ToBeIncluded
object.foo
41. Mixins
extend is also used to mix instance methods into a class
module ToBeIncluded
def foo
"bar"
end
end
class MyClass
extend ToBeIncluded
end
MyClass.foo
42. Mixins
module StringHelpers class Over9000Number < Number
def stringify include StringHelpers
if self.value > 9000
"Over 9000!" def initialize(value)
else super(value + 9000)
"Meh" end
end
end def status
end "Current status: "+stringify
end
class Number end
attr_reader :value
number = Over9000Number.new(42)
def initialize(value) number.status
@value = value # => "Current status: Over 9000!"
end
end
Inherit from one class, include functionality from multiple modules — mixins !
43. Inheritance, Mixins and Design
Ruby allows you to right code once and inject it into multiple places
When to use each?
Inheritance
You should be able to replace a parent object with a child object, honoring
its contract
A child object is a kind of the parent (an apple is a fruit)
In the real world, strict hierarchies are restrictive... we need composition!
Mixins
For composition: A has a B, or A uses a B
Exclusively using mixins can be messy — both should be combined
44. Inheritance, Mixis and Design
Each of them serves its purpose, our job is to use the appropriately
None of these make any sense:
class Person < DataWrapper
end
class Banana
include FruitProperties
end
Think before you type
46. Reflection
Ruby is very powerful when it comes to examining the aspects of a program
within itself
It can discover information about:
What objects exist
All class hierarchies
Attributes and methods of all objects
Information about methods
“When you introspect, you think about your thoughts and feelings. This is interesting because you're using thought to
analyze thought.” — Dave Thomas
47. Reflection and Objects
You can transverse all living objects:
ObjectSpace.each_object(Float) { |x| puts x }
48. Reflection and Objects
You can look inside objects:
[1,2,3].methods[0..4]
[1,2,3].respond_to?(:to_s)
[1,2,3].kind_of?(Hash)
49. Reflection and Objects
You can invoke any method by name using send :
a = [1,2,3]
a.send(a.private_methods.first.to_sym) # "initialize"
a # now it's empty!
50. Reflection and Objects
Another way is to use the Method class:
a = [1,2,3]
constructor = a.method(:initialize)
constructor.call
a
You get a Method object which you can store, pass around and call anytime!
51. Reflection and Classes
It's also possible to look inside classes:
String.ancestors # all superclasses and mixed-in modules
52. Reflection and Classes
It's also possible to look inside classes:
klass = String
result = klass.to_s
begin
klass = klass.superclass
result += " < " + klass.to_s
end while klass.superclass
result
53. Reflection and Classes
It's also possible to look inside classes:
Fixnum.constants
Fixnum.class_variables
Fixnum.singleton_methods(false)
Fixnum.instance_methods(false) # private/protected/public prefix
54. Reflection and the Program's
Execution
Ruby let's you look at the interpreter while it executes your code
set_trace_func lambda { |event, file, line, id, binding, classname|
printf "%8s %s:%s-2d %-15sn", event, file, line, classname, id
}
# all code is traced from now on
And you can also get the current call stack by using calling caller on your
methods
55. Reflection and the Program's
Execution
You can even get the current source file being executed with the __FILE__ special
variable, leading to an interesting Quine :
print File.read(__FILE__)
A program that outputs itself!
57. Metaprogramming
Ruby code can modify aspects of its own structure at runtime and it makes it all
easy
You've seen it before on this presentation: remember include and extend? Ruby is
effectively injecting their code into their receiver
58. Singleton Methods
Ruby lets you define methods specific to a particular object
person = "person" # String object
def person.say_hi
"Hi!"
end
person.say_hi
Magic? No. Ruby created an anonymous class (often called singleton class) based
on String and added the method say_hi to it
59. Singleton Class
There are other ways of creating methods in an object's singleton class
person = "person"
class << person
def say_hi
"Hi there!"
end
end
person.say_hi
60. Inherited Visibility
The visibility of an inherited method can be changed
class SuperClass
private
def my_private_method
"U can't touch this"
end
end
class SubClass < SuperClass
public :my_private_method
end
object = SubClass.new
object.my_private_method # not so private anymore
61. Inherited Visibility
What's really happening is that Ruby is inserting a hidden proxy method in the
subclass that invokes the original method with super
class SubClass < SuperClass
def my_private_method
super
end
public :my_private_method
end
super calls can access the parent's method regardless of its visibility
62. Defining Methods
Ruby allows you to define methods at runtime using define_method
class BabyInfo
["cry", "eat", "poop"].each do |baby_action|
define_method(baby_action) do
"Of course, babies #{baby_action}"
end
end
end
baby_info = BabyInfo.new
baby_info.cry
Methods can also be blocked/removed by calling undef_method and remove_method,
respectively
63. Class-level Macros
Ruby has a few class-level macros that generate code behind the scenes
class Laptop
attr_accessor :memory # injects a getter/setter for "memory"
end
If you've used Ruby on Rails, you've probably dealt with associations
class Post < ActiveRecord::Base
has_many :comments
end
64. Eval
Similarly to other languages, eval evaluates the passed Ruby expression(s)
course = "LPOO"
eval "'Hello ' + course + '!'"
65. Instance eval
instance_eval allows you to evaluate Ruby expression(s) in the context of an
instance
string = "cool man cool"
string.instance_eval "def shout; self.upcase; end"
string.shout
Remember: classes are instances of Class, so you can also use instance_eval with
them:
Fixnum.instance_eval "def ten; 10; end"
Fixnum.ten
66. Class eval
As the name implies, class_eval can only be used with classes
It evaluates the code as if you were in the context of the class definition
String.class_eval do
def shout
self.upcase
end
end
string = "cool man cool"
string.shout
Note: eval, instance_eval and class_eval can take blocks as arguments as shown
above
67. Eval evilness
eval is slow
eval is dangerous
eval doesn't generally make sense, given all other metaprogramming facilities
68. Callbacks
Ruby provides hook methods , called by the interpreter when a specific event
occurs
Among all available hook methods —also known as callbacks —are:
Method-related hooks: method_added, method_missing, method_removed, etc
Class/Module-related hooks: const_missing, extended, included, inherited, etc
Object marshaling and coercion hooks
69. Callbacks: method_missing
Ruby allows you to act upon calls to undefined methods by using method missing
class BabyInfo
["cry", "eat", "poop"].each do |baby_action|
define_method(baby_action) do
"Of course, babies #{baby_action}"
end
end
def method_missing(name, *args, &block)
"Nope, babies don't #{name}"
end
end
baby_info = BabyInfo.new
baby_info.surf
70. Callbacks: inherited
Ruby allows your classes to act when they're subclassed
class SuperClass
@children = [] # class variable
def self.inherited(child)
@children << child
end
def self.children
@children
end
end
71. Callback: method calls
Ruby allows you to intercept calls to specific methods:
class Object
alias_method :old_to_s, :to_s
def to_s
result = self.old_to_s
"Your modified to_s returned this (should be '"+result+"')"
end
end
object = Object.new
object.to_s
This isn't a direct hook: you're copying the original method and inserting a hook
by yourself
73. Ruby
Ruby is an incredibly powerful language
Many successful projects/products use it at their core: Ruby on Rails, God,
Redmine, etc
You can add and remove code in a running process, redefine methods on the fly,
change their scope
You can even modify basic types like String, Float or even Class and Object
After using it for a while, you'll notice the lack of flexibility on static languages
like C++ or half-static languages like Java
74. Ruby
This presentation, for instance, is running on a Sinatra application
And most of the shown code was executed in real time
No? Then check this out:
class Lecture
def initialize(name = "TBA")
@name = name
end
def finish
"Finished at #{(Time.now.utc+3600).strftime('%H:%M:%S')}!"
end
end
lecture = Lecture.new("Ruby")
lecture.finish
75. More
Books:
Programming Ruby 1.9 by Dave Thomas
Metaprogramming Ruby by Paolo Perrotta
Mailing lists:
Ruby-Talk (subscribe through Ruby's website)
Ruby-pt (Google Groups)
IRC:
#ruby and #ruby-pt on Freenode