Learning a new programming language comes with its own set of challenges. How do I declare a variable? Where are my booleans? How do my string functions work? Is the naming convention camelCase, PascalCase, snake_case? The list goes on! Over the last few months, I was faced with a new (yet similar) challenge of learning an entirely new programming paradigm: Functional Programming! Along with it, the Clojure Programming Language.

Paradigm Shift

a fundamental change in approach or underlying assumptions.

– Google

Having only ever coded under an Object-Oriented paradigm, there were certain assumptions that I tried to bring along as I journeyed into the Functional world. Classes, objects, inheritance–things I thought I had to have actually turned out to be superfluous to the main idea of the code I was writing.

In many ways, learning functional programming felt like learning to code all over again. What follows are some differences and similarities I've found between the Functional and Object-Oriented paradigms.

Accessors & Mutators

Typically, in an OO language, you would have a class with accessor and mutator methods. The accessors would just perform read-only operations on the object, but the mutators would change the state of the object.

In this first example, we'll explore a basic Person implementation in an OO language: C#.

An OOP Person

public class Person
{
    private List<Item> Inventory { get; set; }
    public Person() => Inventory = new List<Item>();
    public void Equip(Item item) => Inventory.Add(item);
    public double Wealth() => Inventory.Select(i => i.Value).Sum();
}

In this example, Add changes the state of the Person without regard for anything that depends on it outside the scope of the method. Within the OO paradigm, this sort of coupling between data and functions is desired. An object should be managed by its member methods and the rest of the application should know nothing of its implementation. But what does this look like in the Functional world?

An FP Person

Still stuck in my Object-Oriented mindset, I tried to implement this code in Clojure, but had no idea where to start.

State! Where do I store my member properties? How do I initialize a person?

My initial thought was to use global variables to store my state, which proved to be difficult to manage in my tests alone, and would break the moment a second person is created. I couldn't see the obvious solution to this problem until I saw someone else do it, and even then I still tried hanging onto my OO practices.

(ns person)

(defn wealth [items] 
  (reduce + (map :value items)))

That's it–just a single function that sums all the items.

But what about my person? I need an object to store all my data, right?

My pitfall was thinking that I needed objects, classes, and encapsulation, when really all that was necessary was an array and a wealth function to operate on that array. The rest could be implemented using the core array functions.

(-> []
    (conj sword)
    (conj six-fingered-glove)
    (conj florinese-dagger)
    wealth)

Another thing you may have noticed here is how nothing outside the scope of the wealth function is affected. wealth receives a value, items, and returns a value based on that input. It is completely unaware of the world around it.

Data Representation

Another key difference I've seen between the OOP and FP paradigms is how data is represented. In OOP, we generally try to keep our data within the scope of an object and operate only on that object.

public void Main()
{
    var westley = new Person();
    westley.Equip(mask);
    westley.Equip(sword);
    westley.Equip(iocanePowder);
    Console.WriteLine($"Wealth: {westley.Wealth()}");
}

In FP, our data is somewhat exposed, freely flowing throughout the application.

However, that doesn't mean we don't have object-like data structures. If we needed to represent some person "objects", we would use some key-value data type like a hash-map!

(def people
  [{:kind :person
    :name "Fezzik"
    :inventory [{:kind :item :name "Holocaust Cloak" :value 24}]
    :strength 389
    :charisma 4
    :dexterity 2}
   {:kind :person
    :name "Inigo Montoya"
    :inventory [{:kind :item :name "Six-Fingered Sword" :value 500}
                {:kind :item :name "Brandy" :value 8}]
    :strength 11
    :charisma 21
    :dexterity 73}
   {:kind :person
    :name "Miracle Max"
    :inventory [{:kind :item :name "Miracle Pill" :value 65}
                {:kind :item :name "Bellows" :value 32}]
    :charisma 86
    :strength 2
    :dexterity 17}])

And if we absolutely need to have member functions, we could accomplish that by adding fields for them...

=> (def vizzini
     {:kind :person
      :name "Vizzini"
      :intelligence 5000
      :reassure #(println "Absolutely, totally, and in all other ways, inconceivable.")
      :greet #(println "We are but poor circus performers.")})

=> ((:reassure vizzini))
"Absolutely, totally, and in all other ways, inconceivable."
=> ((:greet vizzini))
"We are but poor circus performers."

This is a bit ugly though... what we really want is to be able to write something like (reassure vizzini) or (greet vizzini). Lucky for us, Clojure provides several ways of doing this!

Interfaces

Interfacing is another big concept in OO languages. These are used as a façade to perform the same kind of operation on objects of different types.

public interface ISpeakable
{
    void Rename(string name);
    void Reassure();
    void Greet();
}

In FP, namely Clojure, we have a couple ways of achieving interface-like behavior: Protocols & Multimethods.

Protocols

Protocols are sort of like Clojure's version of interface. Just like any other interface, it has a name and a set of function signatures.

(defprotocol ISpeakable
  (rename [_ _] "Renames the speaker")
  (reassure [_] "Makes a reassuring statement")
  (greet [_] "Announces a polite greeting"))

Once the protocol is defined, it can be applied to any defrecord implementation and still look like an ISpeakable.

The use of defrecord in Clojure is very similar to that of class in OO languages. While this data type leans away from the typical Functional pattern, they can sometimes be the best tool for the job.

(defrecord Nemesis [name father]
  ISpeakable
  (rename [this name] (set! (.name this) name))
  (reassure [_] (println (str name " reassures: I swear on the soul of my father, " father ", you will reach the top alive!")))
  (greet [_] (println (str "Hello. My name is " name ". You killed my father. Prepare to die."))))

This code allows us to create an object in memory with :name and :father properties, as well as three member methods. When we instantiate and use Nemesis, we can see the state of the object being modified when rename is invoked.

(defn -main [& args]
  (let [inigo (->Nemesis "Inigo" "Domingo Montoya")]
    (reassure inigo)
    (rename inigo "Inigo Montoya")
    (greet inigo)))

=> "Inigo reassures: I swear on the soul of my father, Domingo Montoya, you will reach the top alive!"
=> "Hello. My name is Inigo Montoya. You killed my father. Prepare to die."

Multimethods

While interfaces dispatch a group of functions based on an object's type, multimethods dispatch single functions based on a predefined rule unique to each multimethod. With this, we can achieve interface-like behavior in a functional language.

(defmulti attack (fn [person _] (:combat-style person)))
(defmulti speak (fn [person _] (:condition person)))

(defmethod attack :brawler [person target]
  ; Fight as God intended: skill against skill
  )
(defmethod attack :swordsman [person target]
  ; Counter Thibault with Agrippa
  )

(defmethod speak :deaf [person message]
  ; Replace 'r' with 'w', 'th' with 'ff', ...
  )
(defmethod speak :inflamed [person message]
  ; CAPITALIZE EVERYTHING
  )

=> (speak archdean "Marriage is a dream within a dream.")
"Mawidge is a dweam wiffin a dweam."
=> (speak fezzik "I am the Dread Pirate Robers and there will be no survivors.")
"I AM THE DREAD PIRATE ROBERTS AND THERE WILL BE NO SURVIVORS."

Similarly to interfaces in OO, we can group together several multimethods to encapsulate the finer details of certain behaviors, keeping our higher-level code oblivious to the implementation. We can see here that the invocations of speak only know about the function interface and does not need to know how the object being passed in affects the output.

In Conclusion...

There are a lot of areas where Functional and Object-Oriented programming contrast, but they are quite similar in other ways. The difference really lies in the way you think about and structure the software.

While you can absolutely do Object-Oriented programming in a Functional language, and vice-versa, it really helps to use a language designed for that paradigm. But if you like to go against the grain, then I encourage you to try using one paradigm in a language designed for another!