You are on page 1of 104

o f

o n
S Better Ruby Through
^
Functional
Programming
@deanwampler
dean@deanwampler.com
polyglotprogramming.com/papers

All images are 2008-2009, Dean Wampler, except where otherwise noted.
This is the second version of the RubyConf2008 talk I did. Here, Ill summarize FP more
quickly and dive into some specific application areas for FP.
Im not just about Ruby...

Co-author,
Programming
Scala
programmingscala.com

Available now.
Why
Functional
Programming?
3

Why, after 50 years in academic and some industry labs is FP going mainstream??
4 2006 Clayton Hallmark
Weve hit the end of scaling through Moores Law.
5 2006 Intel
so, were scaling horizontally.
Single-
Threaded
Code
Wont
Do...
6

Single Threading cant scale anymore...


How do we write robust,
concurrent software?? 7

So we have to write concurrent code, but this is hard!!


Functions,
as in Mathematics

Functional
Programming
8

Functional Programming is based on the behavior of mathematical functions and variables.


Immutability
Characteristics of FP
9

1st characteristic of FP: immutable values.


y = sin(x)
Setting x fixes y
values are immutable
10

Functional Programming is based on the behavior of mathematical functions.


Variables actually reference immutable values.
20 += x ??
We never modify
the 20 object
11

Functional Programming is based on the behavior of mathematical functions.


Variables are actually immutable values.
Concurrency
No mutable values
nothing to synchronize
12

No mutable values: There is changing state in a functional program; its in the stack or new
values returned by functions. Individual bits of memory are immutable.
FP is breaking out of the academic world, because it offers a better way to approach
concurrency, which is becoming ubiquitous.
When you
share mutable
values...
Hic sunt dracones
(Here be dragons)

13

Your alternative, multi-threaded programming, which is very hard and few people can do it
well.
A client wont
mess up an
immutable object.
Another benefit...
14

You can feel confident passing an immutable object to a client without fear that the client
will modify the object in some undesirable way.
Immutable
Types in Ruby?
15

One way to be pure FP is to make your types immutable. (You can also freeze individual
objects, but you must also freeze the attributes, recursively!)
Read-only attributes
class Person
attr_reader :first_name,
Array :last_name,
:addresses,
def initialize fn, ln, ads
@first_name = fn.clone.freeze
@last_name = ln.clone.freeze
@addresses = ads.clone.freeze
end
end
Clone and freeze subobjects
16
First, we can support immutable types by using attr_reader only.
Dont forget to clone and freeze collections and other subobjects. The string attributes have
to be frozen, too. (We should actually clone and freeze each address itself, recursively.)
dean = Person.new(
"Dean", "Wampler", [addr1, ]
p dean.first_name # => "Dean"
p dean.addresses # => ["addr1"]

dean.first_name = "Bubba"
# => undefined method
dean.addresses[0] = "new addr"
# => can't modify frozen

17

First, we can support immutable types by using attr_reader only.


class Person
Mutable!!
def update! vals
@first_name =
vals[:first_name] ||@first_name
@last_name =
vals[:last_name] || @last_name
@addresses =
vals[:addresses] || @addresses
end
end
Use ! methods with caution.
18

Second, dont allow self-modifying methods! (Here we pass in a hash with fields that might
change. We update the field if a corresponding hash value is present.) Note that this method
would be allowed despite the clone and freeze calls we make in the initializer.
Immutable
class Person is better

def update vals
Person.new(
(vals[:first_name]||@first_name),
(vals[:last_name] || @last_name),
(vals[:addresses] || @addresses))
end
end
Returns a new Person

19

Here we merge the has vals with the existing Persons values to create a new Person.
p1 = Person.new Dean,
Wampler, [a1, a2]

p2 = p1.update :addresses =>[a3]


puts p2.inspect

=> #<Person:, @addresses=["a3"]>

Example
20

Here we merge the has vals with the existing Persons values to create a new Person.
Immutable
Objects in Ruby?
Use #freeze
21

You may want immutable instances, but not types. Use #freeze, but to do it properly youll
need to override #freeze and also call freeze on the attributes, recursively!
Drawback:
Overhead of
Object Creation.
Hash#merge
vs.
Hash#merge!

22

Youll create lots of copies of objects if they are immutable. Big objects are expensive to
copy. General Note: As always, any design choice has pluses and minuses.
Functions are
First Class Values
Characteristics of FP
23

2nd characteristic of FP.


tan(x) = sin(x)/cos(x)

Compose functions of
other functions
first-class values
24

Function are first-class values; you can assign them to variables and pass them to other
(higher-order) functions, giving you composable behavior.
First-Class
Functions
in Ruby?
25
Is this a function?
def even?(n); n % 2 == 0; end

[1,2,3,4].select {|n| even? n}


# => [2, 4]

# The following dont work:


[1,2,3,4].select(even?)
[1,2,3,4].select(&even?)
26

You cant just pass the method name, even the proc-ized form, because of Rubys parse
rules. Is it supposed to first invoke even? with no arguments and no parentheses and pass
the result to find_all. Or, is it supposed to pass even? as a value? Ruby wants to call even?
first.
Blocks, not methods, are
first-class functions
in Ruby.
[1,2,3,4].select {|n| even? n}
# => [2, 4]
27

So blocks/procs are the first-class functions of Ruby, but not methods.


But you can do this:
def even?(n); n % 2 == 0; end

[].select(&method(:even?)) #1.8
[].select(&:even?) #1.9
# => [2, 4]

# Recall these dont work:


[1,2,3,4].select(even?)
[1,2,3,4].select(&even?)
28

Note that we can retrieve the method and proc-ize it.


Functions are
Side-Effect Free
Characteristics of FP
29

3rd characteristic.
y = sin(x)
Functions dont
change state
side-effect free
30

A math function doesnt change any object or global state. All the work it does is returned
by the function.
Side-effect free:
easier to
understand the
behavior. 31

A side-effect free function is much easier to understand. You dont have to understand global
or object state to understand its behavior. Functions that modify state are much trickier.
Side-effect free:
easy to call it
concurrently.
32

Much less likely to have concurrency issues.


Side-effect free:
location
transparency.
33

Therefore, you can call it anywhere and the behavior is the same for the same inputs. You can
also replace the call with the value it would return (for a give set of inputs), i.e., in
memoization (i.e., caching).
Side-Effect
Free Functions
in Ruby?
34
class Person
attr_reader :first_name, ,
attr_accessor :addresses

def initialize first_name,



end
end Which ones are side-effect free?

def filter array


array.select {|s| yield s}
end 35

Theres no mechanism to verify that a given method or block is side-effect free. You have to
do it manually, if you can. Note that construction of objects isnt side-effect free, but its an
exception we can live with, if were careful! In a concurrent environment, select is side-
effect free, but what if the array is modified by another thread? What if the implicit block
has side effects?
Recursion
Characteristics of FP
36
Fibonacci Sequence

{ 0, if n = 0
F(n) = 1, if n = 1
F(n-1) + F(n-2), if n>1

37

A better example of a recursive mathematical function.


def fib n case matching
case n
when 0..1 then n
else fib(n-1) + fib(n-2)
end
end

Looks like the math definition!


38

An elegant definition. Note that we are using a case statement. Pattern matching is an
integral part of most functional languages.
Warning:
Recursion can
blow the stack.
Ruby 1.9.1 has tail-call optimization
39

Two drawbacks. Ruby 1.9.1 does tail-call optimization.


Declarative vs.
Imperative
Programs
40
OO code is more
imperative.

p = Person.new()
p.setJobTitle()
p.pay(p.getSalary()/26 +
salesTeam.getQuarterlyBonus())

41

Imperative code: I tell you how to do the work.


Declarative code: I tell you what I want and you figure it out.
Functional programs
are very declarative.
def fib n
case n
when 0..1 then n
else fib(n-1) + fib(n-2)
end
end
42

Imperative code: I tell you how to do the work.


Declarative code: I tell you what I want and you figure it out.
DSLs are Declarative, too

class Person < ActiveRecord::Base


belongs_to :family
has_many :addresses
end

Active Record
43

Ruby DSLs also have this quality. You say what you want and let ActiveRecord decide how to
do it.
We all know that
DSLs are good...
Functional programming
has many of
the same qualities.
44
Fear not the
mathematics...
45

You dont have to be a math wiz to benefit from functional thinking...


Apply its
useful lessons.
46

Lets deep dive into some general lessons from FP.


More
Precise
Objects

47

In many mature code bases, I see ill-defined, hard-to-maintain object models. Can functional
thinking help?
Consider an
algebraic type:
Point
For 2-dimensional points
48

Lets look at 2-dimensional points for a minute.


class Point
attr_reader :x, :y
def initialize x, y
@x = x, @y = y
end

def + p2
Point.new(@x + p2.x, @y + p2.y)
end
def - p2
Point.new(@x - p2.x, @y - p2.y)
end
end
49

A point class with immutable instances (assuming floats for x & y). To add them, you add the
x values and the y values (similarly for subtraction). Also, note that they are immutable. Math
operations create new instances.
describe "Point" do
before :all do
@samples =
(-3..3).inject([]) do |l1,i|
l1 +=
(-3..3).inject([]) do |l2,i|
l2 << Point.new(1.1*i,1.1*j)
end
end
end

50

Before all, create a list of samples, combinations of x and y values between (1.1 * -3) and (1.1
* +3).
describe "Point" do

it "should support + ..." do
@samples.each do |p1|
@samples.each do |p2|
p12 = p1 + p2
p12.x.should == p1.x + p2.x
p12.y.should == p1.y + p2.y
end
end
end // - is similar
51

Then we test all the permutations to ensure that the algebraic properties hold for all
instances (at least all that we try).
For all instances,
certain properties
are true.
52

Data types in functional programs are typically like this.


Objects should
have well-defined
states and state
transitions. 53

FP makes us think about how well defined our objects are. Do we always know the states they
can be in? Are the state transitions (behaviors) well defined?
Example #2

Money
is also
algebraic.
54

Closer to home for some of you, if youve worked on financial apps.


Represent Money as:
A String?
A Fixnum?
A BigDecimal?
or ...
55

For simple things, its tempting to use primitive types like Strings. Theres only one
attribute (the value), right? But, is that the best idea?
class Money
Should do error
def initialize v
checking on v...
@value = v
end

protected Dont leak


attr_reader :value the implementation

def to_s # assume Dollars...


format("$%.2f", value)
end

56

Ignores currency conversions, checking that the passed-in initial value is a float, accuracy -
what should the type of @value be, etc.
Note that the value reader method is protected. We dont want the implementation to leak.
(tests can use money.send(:value) )

# TODO: proper rounding...
def + other
Money.new(value + other.value)
end

def - amount
Money.new(value - other.value)
end

def * factor
Money.new(value * factor)
end
57

Ignores financial standards for accurate rounding.



include Comparable

def <=> other


# TODO: proper rounding...
value <=> other.value
end
end

58

Completes the minimum requirements for algebraic behavior. (We omitted this method from
Point earlier, but it should be there.)
The specs are
similar to those
for Point.
59
Other Examples
A Zip Code?
A persons Age?
An email or street Address?
...

60

For simple things, its tempting to use primitive types like Strings. Is that the best idea?
Compose
domain models
from precise
building blocks. 61

Start with the building blocks, get them precise, then build up.
Bloated, Rigid
Object Models

62

In many mature code bases, I see bloated, hard-to-maintain object models? Why and can
functional thinking help?
Your Person

My Person
63

How many apps/components in your systems have a different notion of Person (or any
other common type)?
Use one, big
Person type with
all possible fields?
64

One implementation option ==> Bloat!!


Use many, small
Person types?
65

Not bloated, but too ad hoc, gratuitously inconsistent?


Do we need
a Person type,
at all?
66

Does defining a type (or more than one) for Person deliver enough value to justify its
existence? This may seem to contradict what I was just saying about well-defined types and
behaviors, but if a type like person is really just a struct of fields, then maybe it isnt
carrying its weight.
But first...

67

To consider answers to these questions, lets first look at one more aspect of functional
programming...
Functional
Data Types

68

, the classic functional data types and algorithms.


Lists and Maps (Hashes)
my_list = [
"1 Memory Lane",
"1 Hope Drive",
"1 Infinite Loop"]

my_hash = {
:addr1 => "1 Memory Lane",
:addr2 => "1 Hope Drive",
:addr3 => "1 Infinite Loop"}
69

Lists and (optionally) Maps are the most common functional data types. Some FP languages
have others, like Sets.
Classic Operations on
Functional Data Types
list map

filter

fold/
reduce

70

List is the most commonly-used data type. Note that maps and sets can be represented as
lists, too (ignoring performance issues from this implementation choice...).
In Ruby?

Array, Hash, ... map

select/
grep

inject

71

Ruby equivalents.
Back to:
Do we need
a Person type,
at all?
72
Maybe were
better off
without a type!
73

If we have a lot of volatility in the definition of Person, maybe trying to define a single or
multiple Person classes is not really that beneficial. This is especially true if were exchanging
person data between components (as well see).
dean = {
:first_name => "Dean",
:last_name => "Wampler",
:addresses => [
"1 Memory Lane",
"1 Hope Drive",
"1 Infinite Loop"]}

dean[:addresses].each do |addr|
puts "Address: #{addr}"
end
Use a Hash
74

Use a hash instead. Note the synergy with JSON. Could also use a Struct in a similar way.
Other components/systems might use a different hash of values.
Person = Struct.new(
:first_name, :last_name,
:addresses)
dean = Person.new("Dean",
"Wampler", [
"1 Memory Lane",
"1 Hope Drive",
"1 Infinite Loop"])
puts dean
dean.addresses.each do |addr|
puts "Address: #{addr}"
end
Use a Struct
75

Use a hash instead. Note the synergy with JSON. Could also use a Struct in a similar way.
Other components/systems might use a different hash of values.
Software Components?
76

This leads us to the long-term goal of creating true SW components.


What makes
a good
component
model?
77
And why
didnt objects
succeed as
components?
78
Examples of
good
component
models.
79
Digital
Electronics

80 2002 electronics-labs.com
With 2^N on/off signals, all the data and complex behavior of digital electronics has been
possible.
HTTP
Get Connect
Post Delete
Put Head
Trace Options
WiFi/Ethernet, IP,TCP, HTTP, REST
81

HTTP - Very simple protocol. 8 messages, two of which are used for almost everything. Yet,
that was enough to create the explosive growth of the Internet. (Of course, there are other
protocols over Internet, like SMTP, IMAP, and POP, but HTTP drove adoption.)
Common Features

Minimal, simple protocol.


Straightforward encoding
of higher-level information.

82

Features of successful component models.


Objects dont
meet these
criteria!
83

The richness and sophistication of objects has paradoxically undermined their utility as a
component model!
A
Functional
Component
Model 84

I suggest (and actually the FP community has said this for a long time) that a functional-
style model has more of the qualities I just described.
Data Is Represented by
Lists, Maps (and Structs?)
dean = {
:first_name => "Dean",
:last_name => "Wampler",
:addresses => [
"1 Memory Lane",
"1 Hope Drive",
"1 Infinite Loop"]}
Like JSON??
85

We saw this already. Remember the JSON!!


You could also use Ruby Structs
Behavior is composed
from the components
generic primitives and
user-supplied blocks.
File.new("").each_with_index
do |line, n|
printf "%3d: %s", (n+1), line
end
86

A very simple example from the core library. The File component provides customization
hooks through Enumerable and other methods. Note that it provides very generic, reusable
behavior, but controlled ways to exploit those primitives.
Other Examples
in Our Industry
JSON, REST
Caching, Messaging
systems.
Erlang-based NoSQL
DBs (e.g., CouchDB).
87

Some other functional examples in industry.


Functional
Concurrency
in Ruby
88
Actor Model
of Concurrency
Autonomous agents
exchange messages.
89

Finally, lets see an example of writing concurrent applications using actors. Actors dont
share any state. They communicate only through messages. However, the actor might have
its own self-contained state.
class Shape; end

class Rectangle < Shape


def draw
"drawing Rectangle"
end
end Geometric Shapes

class Circle < Shape


def draw
"drawing Circle"
end
end
90

Start with a hierarchy of geometric shapes.


2 Actors:
Current Display

draw
draw
??? error!
exit exit

91
Omnibus Concurrency
require "rubygems"
require "concurrent/actors"
require "case"
require "shapes"

include Concurrent::Actors

Both Actors will start with this code


92

This example uses MenTaLguYs Omnibus Concurrency gem. See also Revactor (Ruby 1.9)
and Dramatis. The Greeting and RequestGreeting objects will be used as the messages sent
between actors.
Actor sending
commands
Message =
Struct.new :reply_to,:body
This actor
def make_msg body
current = Actor.current
Message.new current, body
end

93

This example uses MenTaLguYs Omnibus Concurrency gem. See also Revactor (Ruby 1.9)
and Dramatis. The Greeting and RequestGreeting objects will be used as the messages sent
between actors.
Send messages

display << make_msg Rectangle.new


display << make_msg Circle.new
display << make_msg "Hi, Display!"
display << make_msg :exit

94

Here is the code in action.



display = Actor.spawn do Display
loop do Actor
Actor.receive do |msg|
msg.when Message[Object,Shape] do |m|
s = m.body.draw
m.reply_to << "drew: #{s}"
end
msg.when Message[Object,:exit] do|m|
m.reply_to << "exiting"
end
msg.when Message[Object,Object] do|m|
m.reply_to << "Error! #{m.body}"
end; end; end; end

Here is the main Actor code. An actor is spawned (e.g., in a separate Thread). It loops
forever waiting to receive messages. It matches each message by type, using MenTaLguYs
Case gem, which enhances #===. Note the combination of pattern matching and
polymorphism (Shape.draw).

display = Actor.spawn do Case gem:
loop do #===
Actor.receive do |msg|
msg.when Message[Object,Shape] do |m|
s = m.body.draw
m.reply_to << "drew: #{s}" messages
end
msg.when Message[Object,:exit] do|m|
m.reply_to << "exiting"
end
msg.when Message[Object,Object] do|m|
m.reply_to << "Error! #{m.body}"
end; end; end; end

96

Here is the main Actor code. An actor is spawned (e.g., in a separate Thread). It loops
forever waiting to receive messages. It matches each message by type, using MenTaLguYs
Case gem, which enhances #===. Note the combination of pattern matching and
polymorphism (Shape.draw).

display = Actor.spawn do
loop do
Actor.receive do |msg|
msg.when Message[Object,Shape] do |m|
s = m.body.draw
m.reply_to << "drew: #{s}"
end
msg.when Message[Object,:exit] do|m|
m.reply_to << "exiting"
end
msg.when Message[Object,Object] do|m|
m.reply_to << "Error! #{m.body}"
end; end; end; end

97

Here is the main Actor code. An actor is spawned (e.g., in a separate Thread). It loops
forever waiting to receive messages. It matches each message by type, using MenTaLguYs
Case gem, which enhances #===. Note the combination of pattern matching and
polymorphism (Shape.draw).
Receive replies
loop do
Actor.receive do |reply|
reply.when "exiting" do |s|
p "exiting..."
exit
end
reply.when String do |s|
p "reply: #{s}"
end
end
Back to the driver script
98

Here is the code in action.


"reply: drew: Rectangle"
"reply: drew: Circle"
"reply: Error! Hello Display!"
"exiting..."

99

What gets printed.


Pattern matching
is used in FP
like polymorphism
is used in OOP.

100

The actor example shows functional-style pattern matching, which is used heavily in FP in an
analogous way that polymorphism is used in OOP.

display = Actor.spawn do
loop do
Actor.receive do |msg|
FP pattern matching...
msg.when Message[Object,Shape] do |m|
s = m.body.draw

end with OO
polymorphism

A powerful combination!

Note the combination of pattern matching and polymorphism (Shape.draw) is very powerful.
Finally...

102
Finally...
Learn a
Functional
Language.
103
Thanks!
dean@deanwampler.com
@deanwampler

polyglotprogramming.com/papers
programmingscala.com

104

You might also like