Joel McCracken
@JoelMcCracken
Software Developer at Think Through Math
Functional programming enthusiast
Three questions to answer:
What do we mean by functional programming? Why care? How do we do functional programming in Ruby ?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.) -
A model, example, or pattern A way of thinking about programsSome 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:
(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?