Functional Programming with Ruby: Procs, Lambdas, Closures and Functions

Functional Programming with Ruby: Procs, Lambdas, Closures and Functions

Last updated:
Functional Programming with Ruby: Procs, Lambdas, Closures and Functions
Source
Table of Contents

Functional Programming INTRO

Functional programming is a programming paradigm that emphasizes coding with immutable variables, higher-order functions, avoiding nonlocal side-effects and recursive functions.

From my experience, this definition doesn't help much to understand what it's all about, and you need to play around with functional programming languages (e.g. Haskell or Scala) for the advantages and disadvantages of this method to become apparent. The other, more common programming paradigm is imperative programming which is what you have probably used so far.

That said, it does not mean that you need to pick and choose one paradigm over the other and live with it for better or worse. Many languages (including Ruby) expose tools that allow us to write functional code where it makes sense to do so but does not prohibit the use of normal, imperative programming constructs that most of us are used to.

It is possible to use functional principles in all programming languages.

Ruby block parameters and the yield keyword

HIgher-order Functions are one of the cornerstones of functional programming, but they're fairly easy to understand. A higher-order function is a function that simply takes a function as parameter or returns a function as its result when called.

Higher order functions are functions that take functions as parameters or return functions as a result

One of the ways Ruby lets you pass functions as arguments is through using blocks. For example, you can use Array#each as follows:

arr = [1,2,3,4]

arr.each{ |el| print el * 2 }
# prints 2468

and another example with Array#select for filtering an array:

arr = [1,2,3,4,5,6]

print arr.select{ |el| el % 2 == 0 } # select even numbers
# prints [2, 4]

The block (within curly braces) was effectively passed as a parameter to method each. You can send a block as a parameter to any method that uses the yield keyword. Yield passes control to the block passed as parameter.

  • A method that only prints a number if it makes the given block return true:

    def print_if(number)
        if yield number
            print number
        end
    end
    
    # prints nothing because 4 is not equal to 5
    print_if(4){ |el| el == 5 }
    
    # prints 4 because 4 is even
    print_if(4){ |el| el % 2 == 0 }
    
    # prints 2 because true is always returned
    print_if(2){ |el| true  }
    

Procs

Passing blocks are one way to pass functions as arguments to other functions but that's not all Ruby has for you. You can also declare functions and put them into variable and do anything you would with a normal variable or value.

They're called procs in Ruby and this is how you use them:

  • Sending procs as arguments to functions

    # creating a proc that takes two number and returns their sum
    sum = proc{ |a,b| a+b }
    
    # calling the proc
    res = sum(3,4)
    print res # prints 7
    
    # the proc is just another parameter
    def some_function(x,y)
    # ...
    end
    
    some_function(sum,"foo bar")
    
  • Returning procs from functions

    # procs can also be returned from functions
    def proc_generator(multiplier)
        proc{|num| num * multiplier }
    end
    
    # create a function that multiplies something by three
    times_three = proc_generator(3)
    
    # and use it
    print times_three(4) # prints 12
    

There's 3 ways to create a Proc in Ruby: Proc.new{}, proc{} and also lambda{}

Extras

You can define you own keywords to declare a block

the 'λ' character has unicode 3BB

def λ(&blk)
    Proc.new(&blk)
end

by_four=λ{|num| num * 4}

puts by_four.call(3) # prints 12

Resources

Dialogue & Discussion