Connecting...

Pexels Photo 1073771

From Objects to Functions an exclusive article by Severin Perez

Pexels Photo 1073771

We are thrilled to share this article written by Developer Severin Perez exclusively for Signify! This is a great introduction to Functional Programming 'From Objects to Functions', Severin looks at how we can take what we learned from the OOP solutions and convert them to alternate FP solutions in Clojure. Happy learning!

 
 

From Objects to Functions

An Introduction to Functional Programming

                                                                                                     The Lambda Symbol

 

One of the best things about programming is that there are many ways to solve the same problem. Ironically, this is also one of the most difficult things about programming. For new programmers, the seemingly endless array of design patterns, best practices, techniques, principles, and all other manner of prescriptive dogma is intimidating at best, and deeply demoralizing at worst. But there is good news hiding among the panoply of architectural choices — as different as they may seem, all such patterns eventually lead back to the same foundational principles. Once you’re familiar with one way to solve a problem, others will follow more easily.

To illustrate how different techniques can be used to solve the same problem, this article will review two different programming paradigms: object-oriented programming (OOP); and, functional programming (FP). Since the vast majority of programmers learn OOP first, we will start by implementing our example programs using OOP patterns in Ruby. From there, we’ll look at how we can take what we learned from the OOP solutions and convert them to alternate FP solutions in Clojure.

 

A Brief Introduction to Functional Programming

Functional programming is, unsurprisingly, all about functions. If you’re coming from the OOP world, then you may find it useful to frame the transition this way: if the fundamental units of work in OOP are objects, then the fundamental units of work in FP are functions. Just as objects encapsulate some piece of  behavior  in OOP, functions do the same in FP. Each function in your program carries out some distinct piece of work, and taken together, these functions jointly accomplish the overall task of the program. Of course, OOP languages use functions/methods as well; however, there are some critical elements that distinguish functions in FP languages:
  • Functions are Pure: A pure function is one that depends exclusively on its inputs when determining its output. In other words, if you provide a pure function with the same inputs over and over, you will always get the same output. Put another way, pure functions have no side effects — only return values. Compare this to functions in OOP, which may be reliant on some piece of external state. For example, a method on a particular object might access and/or change a separate property held on that object. As a result, the return value from that method could change depending on the current value of the related property. In FP, functions do not depend on external state — they only depend on their arguments.
  • Functions are First-Class: A first-class entity is one that can be passed as an argument to a function. If a language has first-class functions, then they may be passed to other functions as arguments (and by extension, they may receive other functions as arguments). This is absolutely critical in FP because it means that functions may be defined and then passed around a program as a means of interacting with one another.

  • Functions are Higher-Order: A higher-order function is one that either takes another function as an argument or outputs a function as its return value. This concept is an extension of the idea of first-class functions and is used throughout FP. In essence, a higher-order function is one whose behavior may be configured by an input function because it can call that input function in the course of its own execution. As a result, a higher-order function carries out different kinds of work depending on the behavior of the function provided to it as an argument. Common examples of higher-order functions are filtering, mapping, and reducing functions.

If the fundamental units of work in OOP are objects, then the fundamental units of work in FP are functions.

If functions are the core unit of work in FP, then how do they fit together to accomplish a larger task? The answer to this question can be hard to picture in words, which is why we will shortly turn to an example; however, FP programs are generally constructed by linking functions together such that the return value of each function is passed into the next as an argument. In this fashion, larger tasks may be carried out by building a chain of smaller tasks that eventually lead to a final result. A useful way of picturing this is like a math equation (in fact, functional programming has its roots in mathematical logic, specifically, lambda calculus.) Consider the following equation:
 
'(2 + (8 * 5)) / 7 = 6 '
 

Here we have a list of linked equations, each of which is reliant on the output of a previous equation. The parentheses in this list help us to determine which “functions” in the equation should be carried out first, their results then acting as arguments in the next “function”. For example, the '8 * 5' equation is executed and the result is then passed in as an argument to the '2 + (previous-result)' part of the equation. The result of that equation is then used as an operand in the '(previous-result) / 7' equation. By working together, these “functions” give us the final result of '6'. Indeed, we can rewrite this mathematical equation as a set of pseudo-code programming functions:

'divide(add(2, multiply(8, 5)), 7)'

This leads us to two other important FP concepts: referential transparency; and, immutability. A function that is referentially transparent is one that may be replaced with its return value without altering the behavior of a program. Looking at the above set of functions, we could replace the call to 'multiply(8, 5)' with '40' and the final result of the program would be the same. The reason we can do this is that none of these functions rely on external state. In FP, values used in the program should be immutable. If the number '8' was held in a variable, 'my-num', and used as an argument, as in 'multiply(my-num, 5)', then it could be used repeatedly in the same function and always produce the same result because it is immutable. In the above example, this is fairly self-evident; however, this same principle applies to more complex data structures, which should also be treated as immutable in FP.

 

A Simple Example

Now that we have a good baseline by which to understand an FP program, let’s look at a simple example. In this example, we will write a program to analyze a set of numbers and output the factorial of each number. First, an OOP implementation in Ruby:

class NumberAnalyzer
  attr_accessor :numbers
  
  def initialize(numbers)
    @numbers = numbers
  end
  
  def print_factorials
    numbers.each do |num|
      puts "#{num}! = #{factorial(num)}"
    end
  end
  
  private
  
  def factorial(n)
    if n == 0
      1
    else
      n * factorial(n - 1)
    end
  end
end

analyzer = NumberAnalyzer.new([0, 1, 2, 3, 4, 5, 10])
analyzer.print_factorials
  # Prints:
  # 0! = 1
  # 1! = 1
  # 2! = 2
  # 3! = 6
  # 4! = 24
  # 5! = 120

In this snippet we have a 'NumberAnalyzer' class that takes an array of numbers as a constructor argument and saves them in a '@numbers' instance variable. The class also has a public 'print_factorials' method that iterates over each number in '@numbers', uses a private 'factorial' method to calculate the current number’s factorial, and then prints out a result string. As you can see, we get the result we expect when we call this function.

One thing to note in this snippet is that the 'NumberAnalyzer' class has a setter method for '@numbers'. Although we do not do so here, we know intuitively that if we were to subsequently use this setter to change the content of '@numbers', then a call to 'print_factorials' would result in a different output. The reason is that 'print_factorials' relies on state held in its particular instance of 'NumberAnalyzer'. If this state changes, so too does the output.

Now let’s take a look at an FP solution written in Clojure:

(ns number-analyzer.core)

(declare print-factorials)

(defn -main [& args]
  (print-factorials [0 1 2 3 4 5 10]))
    ; Prints:
    ; 0! = 1
    ; 1! = 1
    ; 2! = 2
    ; 3! = 6
    ; 4! = 24
    ; 5! = 120
    ; 10! = 3628800

(defn- factorial [n]
  (loop [n   n
         acc 1]
    (if (< n 2)
      acc
      (recur (dec n) (* acc n)))))

(defn print-factorials [numbers]
  (doseq [num numbers]
    (println (str num "! = " (factorial num)))))

Don’t worry if the above looks unfamiliar — syntax in Clojure is a bit different from other languages. For now, what’s important is not the Clojure-specific syntax, but rather, the overall flow of the program. Essentially, this snippet operates as follows:

  • The 'main' function is automatically executed when the program starts.
  • In 'main', we have a call to the 'print-factorials' function, passing in a vector (array) of numbers.
  • In 'print-factorials' we use the 'doseq' function to iterate over an input list, iteratively binding each item from 'numbers' to a local 'num' variable.
  • On each iteration, we pass 'num' to the 'factorial' function and then pass the result into the 'str' function, which builds an output string, which we then pass to the 'println' function, which prints the string to the screen.
Note how the functions flow into one another — the return value of one is the argument to the next. In this fashion we are able to build a larger program out of smaller pieces. Further, we aren’t relying on state. Unlike in the OOP example, where the 'print_factorials' method relied on access to the '@numbers' instance variable, the FP version relies entirely on input values. No matter how many times we call 'print-factorials' in the FP version, it will have the same output so long as we provide it with the same input. You might argue that we could build a similar version using immutable values in Ruby, and certainly that is true; however, the important point here is that in FP we have to use immutable values.
 
 
A Slightly More Complex Example

As we’ve seen, OOP and FP programs can be used to solve simple problems in similar ways. Both rely on a series of functions/methods to carry out their work; however, overall program structure and the use (or lack of use) of state differs. Let’s now turn to a slightly more complex example and see if we can observe the same principles at work. In this example, we will build a short program to run an auction. The program has two main tasks: announce the current items available and the overall auction sales; and, run the auction with a list of bids, which will lead to some items being sold and the overall sales total increasing. Here is an OOP version in Ruby:

1 class AuctionHouse
2   attr_accessor :items, :total_sales
3  
4   def initialize(items)
5     @items = items
6     @total_sales = 0
7   end
8  
9   def run_auction(bid_list)
10     handle_bid_list(bid_list)
11     finalize_sales
12   end
13  
14   def announce_status
15     announce_items
16     announce_sales
17   end
18  
19   private
20
21   def handle_bid_list(bid_list)
22     bid_list.each { |item, amount| handle_bid(item, amount) }
23   end
24  
25   def handle_bid(item, amount)
26     if item_exists?(item) && accept_bid?(item, amount)
27       @items[item] = amount
28     end
29   end
30  
31   def item_exists?(item)
32     @items.has_key?(item)
33   end
34  
35   def accept_bid?(item, amount)
36     @items[item].nil? || amount > @items[item]
37   end
38    
39   def finalize_sales
40     calculate_total_sales
41     remove_sold_items
42   end
43  
44   def calculate_total_sales
45     @items.each_pair do |item, amount|
46       @total_sales += amount unless amount.nil?
47     end
48   end
49  
50   def remove_sold_items
51     @items = @items.select { |item, amount| @items[item].nil? }
52   end
53  
54   def announce_items
55     puts "Available items: "
56     @items.keys.each_with_index { |item, idx| puts "#{idx + 1}: #{item}" }
57   end
58  
59   def announce_sales
60     puts "Total sales: #{dollarize_sales}"
61   end
62
63   def dollarize_sales
64     "$" + @total_sales.to_s.reverse.scan(/.{1,3}/).join(",").reverse
65   end
66 end
67
68 paintings = { "Night Watch" => nil, "American Gothic" => nil, "Tower of Babel" => nil,
69              "Friend In Need" => nil, "Potato Eaters" => nil, "Red Balloon" => nil }
70 auction_house = AuctionHouse.new(paintings)
71 
72 bids = [["Night Watch", 550000], ["Night Watch", 700000], ["American Gothic", 145000],
73         ["Friend In Need", 180000], ["Potato Eaters", 240000], ["Potato Eaters", 300000],
74         ["Red Balloon", 1500000], ["Red Balloon", 25], ["Red Balloon", 1800000]]
75 
76 auction_house.announce_status
77   # Available items: 
78   # 1: Night Watch
79   # 2: American Gothic
80   # 3: Tower of Babel
81   # 4: Friend In Need
82   # 5: Potato Eaters
83   # 6: Red Balloon
84   # Total sales: $0
85
86 auction_house.run_auction(bids)
87
88 auction_house.announce_status
89   # Available items: 
90   # 1: Tower of Babel
91   # Total sales: $3,125,000

Take a few minutes to look through the above example and follow along with its action. For the sake of brevity, we won’t go through each method; however, at its core this program can be described as follows:

  • An 'AuctionHouse' class accepts a list of 'items' when constructing a new instance and saves them, along with an initial 'total_sales' value of '0', to corresponding instance variables.
  • Each 'AuctionHouse' instance has two public methods: 'run_auction', which takes a bid list as an argument and then uses it to mutate the '@items' and '@total_sales' variables accordingly; and, 'announce_status', which announces the available items and the total sales.
  • When we execute the program, we first announce the status before running the auction, then we run the auction, and then we announce the status again. Of note, after running the auction we have mutated the original item list. Every time an auction runs, the state being carried by the auction house changes.
Now let’s turn to an FP implementation of the same program. Again, don’t worry too much about the syntax if you’re not familiar with Clojure. Instead, try to follow the flow of the program. Start with the function calls inside 'main' and then trace each back to its individual elements. Using the function names alone (don’t worry about implementation), see if you can spot how each function takes an input, carries out some work, and then passes the result to the next function to use as needed.
 
1 (ns clojure-auction-house.core
2   (:require [clojure.string :as s :only :join]))
3
4 (declare announce-status run-auction)
5
6 (defn -main [& args]
7   (let [auction {:items { "Night Watch" nil "American Gothic" nil "Tower of Babel" nil
8                           "Friend In Need" nil "Potato Eaters" nil "Red Balloon" nil }
9                  :total-sales 0}
10         bids    [["Night Watch" 550000] ["Night Watch" 700000] ["American Gothic" 145000],
11                  ["Friend In Need" 180000] ["Potato Eaters" 240000] ["Potato Eaters" 300000],
12                  ["Red Balloon" 1500000] ["Red Balloon" 25] ["Red Balloon" 1800000]]]
13     (do
14       (announce-status auction)
15       (announce-status (run-auction auction bids)))))
16
17   ; Available items:
18   ; 1: Night Watch
19   ; 2: American Gothic
20   ; 3: Tower of Babel
21   ; 4: Friend In Need
22   ; 5: Potato Eaters
23   ; 6: Red Balloon
24   ; Total sales: $0
25   ; Available items:
26   ; 1: Tower of Babel
27   ; Total sales: $3,125,000
28
29 (defn item-exists? [items item] (contains? items item))
30 
31 (defn accept-bid? [items item amount]
32   (or (= nil (get items item)) (> amount (get items item))))
33
34 (defn handle-bid [items item amount]
35   (if (and (item-exists? items item) (accept-bid? items item amount))
36     (assoc items item amount)
37     items))
38
39 (defn handle-bid-list [items bids]
40   (loop [bid-number   0
41          items        items]
42     (if (>= bid-number (count bids))
43       items
44       (let [bid        (get bids bid-number)
45             bid-item   (first bid)
46             bid-amount (last bid)]
47         (recur
48           (inc bid-number)
49           (handle-bid items bid-item bid-amount))))))
50
51 (defn dollarize [n]
52   (str "$" (s/reverse (s/join "," (re-seq #".{1,3}" (s/reverse (str n)))))))
53
54 (defn build-items-string [items]
55   (loop [counter  0
56          output   "Available items:\n"]
57     (if (>= counter (count items))
58       output
59       (recur
60         (inc counter)
61         (str output (inc counter) ": " (get items counter) "\n")))))
62 
63 (defn build-sales-string [sales]
64   (str "Total sales: " (dollarize sales)))
65
66 (defn announce-status [auction]
67   (let [items       (:items auction)
68         item-names  (vec (keys items))
69         sales       (:total-sales auction)]
70     (println (str (build-items-string item-names) (build-sales-string sales)))))
71
72 (defn remove-sold-items [items]
73   (into {} (filter (complement (comp some? val)) items)))
74
75 (defn calculate-new-sales [items] (reduce + (filter some? (vals items))))
76
77 (defn finalize-sales [auction]
78   (let [current-items (:items auction)
79         all-sales     (calculate-new-sales current-items)]
80     (assoc auction :items (remove-sold-items current-items) :total-sales all-sales)))
81
82 (defn run-auction [auction bids]
83   (let [pre-bid-items   (:items auction)
84         post-bid-items  (handle-bid-list pre-bid-items bids)]
85     (finalize-sales (assoc auction :items post-bid-items))))

Again, for the sake of brevity, we won’t go through each function, but the general flow can be described as follows.

  • The 'main' function binds two initial values to local variables for our 'auction' and 'bids' respectively.
  • Once the initial variables are bound, we call 'announce-status' and pass in the 'auction' value, which prints the current auction elements (items and sales) to the terminal.
  • We then call 'announce-status' again, but this time, rather than pass in our initial 'auction' value we instead pass in the result of a function call to 'run-auction', with the initial 'auction' and 'bids' values passed to it as arguments.
  • The 'run-auction' function calls 'handle-bid-list', using the pre-bid 'items' and the 'bids' list as arguments. After running through a series of helper functions, 'handle-bid-list' returns a new list of items with the various bids applied and binds it to a local 'post-bid-list' variable.
  • After the bids have been handled, we then pass a new auction map with the new item list to the 'finalize-sales' function, which removes the sold items and calculates the total sales, returning a new auction map with the updated values.
  • Finally, the new auction map gets passed in to our second call to 'announce-status', which prints the new status to the terminal.

If you’re new to FP or to Clojure, the above snippet is likely a bit bewildering, and that’s OK. The most important lessons for now are: the way each function flows into the next one; and, the fact that the initial 'auction' values are never changed during the program, but rather, a totally new auction map is created and eventually printed to the terminal.

 

TL;DR

Object-oriented programming (OOP) and functional programming (FP) are two seemingly opposite programming paradigms; however, at their core, both can be used to accomplish similar tasks. Whereas OOP uses objects as its fundamental unit of work, FP uses functions. In particular, FP functions are notable for because they are: pure, in that they have no side effects; first-class, in that they can be passed around as arguments to other functions; and, higher-order, in that they can be composed together. FP programs are structured such that the result of one function call is passed as an argument into the next, creating a flowing series of  behaviors  that lead to the accomplishment of some larger task. Of note, FP programs do not make use of  state , and in fact, variables in FP are generally immutable. However, appropriate looping constructs can still be used to mimic  state  by creating new objects of the same type.
 
 
This article was written exclusively for Signify Technology by Severin Perez.

Severin Perez is a developer and writer based in Los Angeles, CA. For more of Severin’s writing, you can follow him on MediumTwitter, or his personal website.