Functional

Programming

in

Ruby

About Me

Joel McCracken

@JoelMcCracken

Software Developer at Think Through Math

Functional programming enthusiast

Overview

Three questions to answer:

  1. What do we mean by functional programming?
  2. Why care?
  3. How do we do functional programming in Ruby ?

What do we mean by functional programming?

  • Paradigm
  • Programming with Functions
    • Treat functions as values
    • Create and return functions
    • Morph functions
  • High order functions

What do we Mean by "functions"?

In this talk, a function is a piece of code that:

  • Takes arguments
  • Returns results

Functions in Ruby (?)

  • Ruby doesn't have functions.
  • Many function-like things, primarily:
    • Lambdas
    • Blocks
    • Procs
  • Going to call all of these "functions".

Lambdas


add_10 = ->(x){ x + 10 }
add_10.call 4 #=> 14
          

lambda { |x| x + 10 }
          

lambda do |x|
  x + 10
end
          

Procs


add_5 = proc { |x| x + 5 }
add_5.call 10 #=> 15
          

add_5 = Proc.new { |x| x + 5 }
add_5.call 4 #=> 9
          

Symbol#to_proc


[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.

The Functional Paradigm

Paradigm (n.) -

  1. A model, example, or pattern
  2. A way of thinking about programs

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.

Programming Paradigm Example: Imparative

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.

Programming paradigm example: Object-oriented

Describing the program as the interactions between actors


student = current_student
classroom.add_student student
          

Code sends "add a student" message to classroom.

Programming paradigm example: Functional

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).

Functional paradigm example


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's Many Functions

(Choose your functions wisely)

Ruby is full of function-like things:

  • lambdas
  • blocks
  • procs
  • methods
  • "proc" ducked-typed objects

Methods


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.

"Proc" Ducked-Typed Objects

  • Both procs and lambdas are called through the '#call' method.
  • Our own objects can look like and quack like a proc.

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)
          

The "Block Semantics" Gotcha

Blocks and procs try to behave like plain-ol' blocks which creates some unintuitive behavior.

Some rules to avoid this problem:

  • Avoid explicit 'returns' whenever possible. Use ->{ x + 1 } instead of ->{return x + 1 }.
  • Use lambdas instead of procs.

(If you want to talk about this more, see me after)

Immutability and Functional Purity

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:

  • do not modify anything. Instead, create new instances.
  • do not reference global variables or class-level instance data. Functions should not depend upon the environment they are executed in.

THE END

Questions?