Joel McCracken
@JoelMcCracken
Software Developer at Think Through Math
Functional programming enthusiast
Three questions to answer:
In this talk, a function is a piece of code that:
add_10 = ->(x){ x + 10 }
add_10.call 4 #=> 14
lambda { |x| x + 10 }
lambda do |x|
x + 10
end
add_5 = proc { |x| x + 5 }
add_5.call 10 #=> 15
add_5 = Proc.new { |x| x + 5 }
add_5.call 4 #=> 9
[1, 2, 3, 4, 5].map &:even?
# => [false, true, false, true, false]
&:to_s
# is essentially the same as
proc do |obj|
obj.to_s
end
Very concise and readable.
Paradigm (n.) -
Some paradigms are better suited to problems than others.
The "style" you should use depends upon the problem you are trying to solve.
Let's look at other paradigms for contrast.
Describing the program as a sequence of steps.
require 'argus'
drone = Argus::Drone.new
drone.start
drone.take_off
sleep 5
drone.turn_right(1.0)
sleep 5
drone.turn_left(1.0)
sleep 5
drone.hover.land
sleep 5
drone.stop
Code "tells" the drone each step it should take.
Describing the program as the interactions between actors
student = current_student
classroom.add_student student
Code sends "add a student" message to classroom.
Program described as transformations necessary to perform computation.
average_test_score =
student_tests.map(&:score).
reduce(&:+) /
student_tests.length
Code describes a transformation from one data (a set of tests) to another (the average of those tests).
average_test_score =
student_tests.map(&:score).
reduce(&:+) /
student_tests.length
Lets break it down.
Problem: "Given a set of tests, find the average score."
Solution: "Sum the scores of the tests and divide by the total number of tests"
set_of_scores_of_each_test = student_tests.map(&:score)
sum_of_test_scores = set_of_scores_of_each_test.reduce(&:+)
number_of_tests = student_tests.length
average_test_score = sum_of_test_scores / number_of_tests
Ruby is full of function-like things:
class MySpiffyCollection
def initialize(items)
@collection = items
end
#...
def map(fn)
saved = []
@collection.each do |item|
saved << fn.call(item)
end
MySpiffyCollection.new(saved)
end
#...
end
This method takes a function as and argument, applies it to each item, and returns a new instance of itself containing the results of the evaluation.
class MakeUsersHappy
def call
if some_choice?
do_this
else
do_that
end
end
#... define 'some_choice?', 'do_this', and 'do_that'
end
happy_users = MySpiffyCollection.new(dissatisfied_users).
map(MakeUsersHappy.new)
Blocks and procs try to behave like plain-ol' blocks which creates some unintuitive behavior.
Some rules to avoid this problem:
->{ x + 1 }
instead of
->{return x + 1 }
.
(If you want to talk about this more, see me after)
Ruby is a flexible language and doesn't "force" you to do anything, so some discussions on immutability and purity do not apply.
However, there are a few principles to follow to help. When programming in a functional style:
Questions?