This document discusses functional programming concepts in Ruby including:
- Side effect-free vs side effectful Ruby methods
- Currying and partial function application to break functions into reusable parts
- Recursive functions and tail call optimization
- Thinking in streams to process infinite data lazily
- Generics to build reusable functions
It provides examples of applying these concepts in Ruby and recommends further learning resources.
13. @jleo3
side effects are necessary to do interesting things
● I/O
● raising exceptions
● outputting data to the terminal
● updating a record in a database
● anything else that changes state
14. @jleo3
what do most people mean when they fp?
Remove side effects!
Curry! Generics! Lazy Evaluation!
Recurse! Tail-call optimize!
19. @jleo3
an example
Student#calculate_grade
def calculate_grade(scores, student)
case scores.sum / scores.size
when 90..100
student.update_grade("A")
when 80...90
student.update_grade("B")
when 70...80
student.update_grade("C")
when 60...70
student.update_grade("D")
else
student.update_grade("F")
end
end
Student#calculate_grade
def calculate_grade(scores)
case scores.sum / scores.size
when 90..100
"A"
when 80...90
"B"
when 70...80
"C"
when 60...70
"D"
else
"F"
end
end
21. @jleo3
what is currying?
simple example: add
add = -> (a, b) { a + b } #(1 function, 2 arguments)
curried_add = -> (a) { -> (b){ a + b } } #(2 functions, each with 1 argument)
22. @jleo3
what is partial function application?
● Pass in a number of arguments less than the function’s arity
● Result: a new function with all of the passed-in arguments
applied
add = -> (a, b) { a + b }
add = -> (5, b) { 5 + b } # partially applied; NOT VALID SYNTAX
23. @jleo3
but I’m lazy! i don’t want to have to think about
all of that!
● You don’t have to!
● curry handles both currying and
partial function application.
● add.curry
24. @jleo3
into action with curry and partial function
application
find_multiples = -> (x, arr) {
arr.select { |el| el % x == 0 }
}
25. @jleo3
Unique IRL; Generic in code
● Functions that return functions
● Building blocks for more
specific functions
● Generics are to FP what
Objects are to OOP
26. @jleo3
into action with curry and partial function
application
Generic:
find_multiples_of = find_multiples.curry
Individuated:
find_multiples_of_3 = find_multiples_of.call(3)
find_multiples_of_5 = find_multiples_of.call(5)
27. @jleo3
thinking in streams
Find infinite multiples!
find_first_multiples = -> (num, mult)
{
(1..).lazy.select {
|x| x % mult == 0
}.first(num)
}
28. @jleo3
recurse!
Writing recursive functions is all about determining the “terminal
clause.”
Popular recursive functions:
● Factorial (terminal clause: x <= 1)
● Fibonacci (terminal clause: x <= 1)
● Sum Squares (terminal clause: x == 0)
30. @jleo3
demystifying tail-call optimization
def factorial(x)
return x if x == 2
x * factorial(x - 1)
end
def factorial(x, acc=1)
return acc if x <= 1
factorial(x - 1, x * acc)
end
(RECURSIVE) (TAIL RECURSIVE)
33. @jleo3
Thank you! Also, get @ me!
- joe leo
- twitter/github/linkedin: @jleo3
- defmethod.com
Thank you to…
- everyone at Rubyconf
- Matz
- Abby and the Program
Committee
- David A. Black
Notes de l'éditeur
Insert green checkmarks; swap words for language symbols; animate to show the others one at a time
Yes, Ruby is a functional programming language.
FP is any language that supports functions.
Ruby does this with lambdas and procs
Matz was primarily inspired by Lisp when he created Ruby. Also functional.
Java does it with closures (since Java 7)
PHP supports first-class functions
referential transparency: a function without side effects
animate red x
(examples: upcase vs upcase!; <<)
Animate “Ruby does not”
But wait! What about Object#freeze? What about frozen strings?
At the CRuby freezing an object in Ruby is really just setting a bit.
Since the bit can be flipped, it’s not guaranteed immutable
change text to ruby
(more examples: [1,3,5].fetch(2); .fetch(3))
less code because it’s doing less
updating a db may happen, but it’s not necessary to update an in-memory object (like an active record object)
Holden Caulfield
animate: each function, one at a time
animate: circle add and curried_add - “these are functionally equivalent”
animate: circle 2nd curried_add with note “the ruby way”
these are both lambdas (using stabby lambda syntax)
they are both valid ruby syntax, as we’ll see
curried_add is the curried form of add
but you can’t call curried_add the same way (demonstrate)
used when we know some, but not all, of a function’s arguments
the second line is not valid ruby syntax, but curry achieves the same thing. Which brings me to...
Ingatius J. Reilly
demo find_multiples_of_3 and find_multiples_of_5
For more info on currying and partial function application, see my recent RubyTapas episode
New in Ruby 2.6 you can use the infinite range syntax Float::INFINITY
lazy evaluation is not unique to FP, but delayed evaluation is core to functional programming
allows us to start thinking in terms of “streams of data” rather than finite sets
we can do this without talking about stacks and stack frames!
note the accumulator
The last line of a tail recursive function is a call to the function itself.