A Metaphor For Functional And Object-Oriented Programming
#absurd-abstractions
11-04-2025
This post might be one of the most important articles I write for myself for it's practical implications. It is the culmination of all things I love when thinking about programming, dealing with the conceptualization of the craft of programming.
The difference between object-oriented and functional programming.
On a conceptual level.
I recently got some very nice feedback of a fellow-student from my programme Creative Technologies from which I graduated end of last year, saying that my metaphorical story-like essays on this blog help him to digest programming concepts that are usually out of his reach. This is exactly my goal with the blog, being able to share my convoluted mind while establishing an accessible language for everyone to be able to appreciate the subjects.
Therefore, brace yourself. As I will lead you through a picturesque world of architecture and paradigms.
Let us first try to define what an application is and what it's requirements or components are, before we move into paradigms. For that I propose to understand it from a user perspective and a system's perspective. I think to understand what an application means in terms of these two perspectives reveals a lot of how we structure it.
What is an Application: For the User?
We develop applications to be used by users. As a creative technologist I don't want to limit this definition to the common idea of an application. An art installation therefore needs to fit this definition aswell. Art is about expression for the artist and an experience for the audience. Think video games, interactive art, etc. On the other hand other applications can provide a service. A tool that enables or supports an activity.
Therefore speaking of functionality or functions would be fitting for the latter but not for the former. I would like to propose the first abstraction: feature. I like the word feature because it can refer to any component of an application that makes it distinct from any other. So let's roll with that.
Great, so our application is a self-contained unit, that presents features to a user. Every feature draws from a domain. Generally speaking for artists, the domain knowledge is artistic practice and sensibility or taste. In terms of the service application it is infrastructure, the interface to the hosting system (we come to that in the next section), interaction with the user through a user interface. However behind the art on one side and behind the technical knowledge on the other hides more. The subject of the application.
Let me explain.
You don't use Photoshop because it has a user interface and is supported by windows (well yes, actually. These are the requirements), but because Photoshop can edit a picture. It not only knows how do edit a picture, it knows how to present a well-structured workflow and specific tools to interact with the picture internals. It has content-aware replacement, it has AI inpainting, it has sophisticated selection tools, filters, layers, channels. And many more.
One more example.
You don't play rocket league because it is hosted on steam and reacts to player input (any PC game should do this nowadays), but because it provides a unique experience in which you "bump ball with car and score goal" (Sorry, cavemen english felt appropriate to express the primitive joy I associate with this game).
So while interaction is great, the core of an application is Knowledge! An application is a knowledge base.
So let us piece together an appropriate definition: An application is a collection of features accessible to a user to provide a service or experience, drawing from a central knowledge base.
What is an Application: For the System?
First of all. What do I mean by system? I have chosen System to refer to any environment in which our application is embedded or hosted. On a computer it is an operating system, and the application is a program. If it is the operating system itself, the environment is the bare metal of the machine. If it is a web app it is probably a Server hosting the app and the client viewing it, having to follow browser specifications. In case of the webapp the entire distributed system is the application: some parts are on a server listening to requests, others run locally on each user's client, and there is a database. And for art installation the environment is space and usually some embedded program on a microprocessor.
Your application then itself is a system of components and features, which needs to interface with it's host system and it's environment. Technically the user for our purposes is a system aswell, with mechanical and electro-chemical components.
But more abstractly speaking, in any case, it is a process with multiple components taking place in a host environment. And since it is a process, it needs a start, an end and a recurring main loop or otherwise it will just close once executed.
And lastly, since we are speaking of programmed applications specifically, we have the internal structure of it's instructions. Instructions then call other instructions to achieve a desired effect.
Let me demonstrate with Rocket League in cavemen pseudo code.
Player want to bump car into ball
-> Player move joystick forward
-> Ask ControlScheme what do?
-> Control Scheme say, move forward
-> How move forward?
-> Ask car, you know how to move forward?
-> No, I will ask locomotion
-> Locomotion, what is Position?
-> Acceleration add to velocity, velocity add to position
-> Car move
-> Hit ball yet?
-> Hit ball yet?
-> Yes! Boom
-> Player Happy
Every arrow is probably some procedure calling another procedure, that does exactly that and then delegates to the next procedure. This is done so we don't have to repeat ourselves. There is ideally one place inside our application that encodes the knowledge for one feature.
How these processes are structured, how start, and end are defined, how features or components interact, how they call each other, why and with what effect, is what we will discuss from now on.
But first....
What is the Application for the Developer?
Wait, what? We didn't agree on this! You must think. Sorry, I'll make it short. I promise
So, what is an application for the developer?
It is a continuously growing collection of bugs. Roll the credits...
There you have it. In the beginning no bugs. In the end a full star wars intro of GitHub Issues.
But jokes aside. The variable of change is another crucial element to consider when it comes to selecting a paradigm, because of mantainability.
And now Paradigms!
What are Paradigms?
The Cambridge Dictionary defines a Paradigm as "A set of theories that explain the way a particular subject is understood at a particular time". A programming paradigm then is a collection of concepts and principles by which to structure a computer program. It also defines syntactical requirements for a language to follow (or as I prefer to think of it, the semantics), to be qualified as a language of that paradigm. Ok but what does that mean?
If you conceptualize of your application as a process you would think of it linearly with a start and an end. You would store your state in a globally accessible space and when a certain condition takes place, jump to another point in your instruction list. Well that, in a sense was the go-to paradigm called imperative programming, in the advent of computers. But we have since then moved on from punching holes into cards.
(But take this comment with a grain of salt, and this is the natural way we interact with the CPU itself, and assembly language to this day have to be written like this. Which presents a conceptual challenge in itself.)
With increasing complexity you would go completely ballistics if you had to manage everything in one linear flat list, jumping around from one instruction to the next and store all of your data in one big pile of garbage (what it would turn to). You then start to think of how to structure and organize that collection of instructions and the information you need to store. And this is what happened.
For example you define how your car moves forward in it's own self-contained action. Whenever you want to move the car, you would invoke the name of that procedure at which point it will be executed. Instead of jumping to a new location, we enter into the invoked action. We have added a new concept, a scope of execution
. The same applies for loops and conditional execution, if
and for
statements were introduced to define nested blocks that execute for a certain condition (a boolean value).
This is the procedural structured programming Paradigm.
However that is still not enough to manage complexity. Most data is related to certain functions. So let us pair data with action in their own little component, and call it an object. Then our entire application is a collection of interacting Objects. The domain knowledge then would riside in it's respective object or class (which we will talk more about later). This is what Java and C# do. Where even the entrypoint needs to be modeled as a Object class.
This is the object-oriented paradigm.
Another way of solving this problem is to go the opposite way. To avoid global state we eliminate state all together. Instead of having some state sitting somewhere to be manipulated by the application. The entire application is a chain in which, every function is responsible to transform one value to the next. This way we can even treat the operation itself (the function) as a value, and pass that around as a value. We would then build complex functions out of the combination of simpler functions.
This is the functional paradigm.
I really like the definition of Robert C. Martin that paradigms induce discipline by restrictions. The structured programming paradigm took away the ability to use goto
, enforcing a structured flow of procedures. In purely object-oriented programs you can't define a function outside of the scope of a class. It enforces compartmentalization by design. In functional programming you can't define some state and arbitrarily access it outside the scope of your function. This enforced referential transparency.
Object oriented programming
Let us take a deeper look.
What is an object technically? An object is some space in memory which couples data to an instance. What that instance can do is defined by a Class
. A class doubles down as the template
and the namespace
in which the specific members of the object are defined and what functions are available, or rather what Methods
are available to apply to that object. A class needs to be instantiated into an object through a constructor
, and removed from memory by a deconstructor
. Usually instances refer back to their classes implementation except when they don't.
A class can however also be static without being instanced as object, meaning it just provides a nice space inside the application isolated from the others, in which you put related data and functions.
What is an object conceptually? An object is an entity, something that can be named and has properties with a behavior. Like a car, or a girlfriend. What if in addition to a car in my application, I had a plane, and I want both to have an engine based on locomotion. Only difference is that one of them is in the air, and the other on the ground.
Well then we can say both of them are a Vehicle. A common superclass. My girlfriend at the end of the day is also just a person. Don't tell her that, she would feel heartbroken. She is a special person! There you go, I fixed it. But that doesn't change the fact that she is a person. With a role in society. With a name and birthplace. Any person has that. Every person is also an animal, and as animals are living beings we need basic needs like nutrition and so on.
This phenomenon is called Inheritance
. And the first pillar of object-oriented programming.
Ok but what if in my application I have something that needs to react to a click? Well then let's make it a parent class with Inheritance! But what if I told you that my car needs to be clickable. But car is already a vehicle, so it can go "bump ball". Well lucky me, I can define vehicle as a clickable. First of all: terrible idea. Why would you mix the UI domain with your Knowledge Domain of Vehicles? In your application's knowledge base which is it's distinguishing value you would define a vehicle as a clickable thing that also has an engine and can move? Häh, no. Secondly what if I told you that I don't want the Airplane to be clickable? What more, if I told you that not every car should be clickable either? Well then make a boolean property in your car, silly! Well no, because now my theoretical Pointer object, which just cares about clickables needs to explicitly check if it is a Car and if that car can be clicked. Good luck with scaling that application when you have over 100 different classes to be clicked. What we need is a way to define a protocol by which we can just focus on some feature relevant for a certain purpose. Wait? Do I hear.... Oh no...
ABSTRACTION.
But, yes it's true. That protocol is called an Interface
and the principle we applied is called Abstraction. This allows polymorphism
, in which the single object can be different things for different contexts, which is a consequence of abstraction. Like a slap is a consequence of telling my girlfriend she is just an animal that requires nutrition. Abstraction is the second pillar of object-oriented programming, and the worst false friend a programmer can have. I will say it here once (screaming) "Abstraction is not exclusive to Object-Oriented programming!"
Now, car bumps into ball. When car hits the ball, all car wants to do is tell ball that it was hit. Probably through another interface called Hittable
or Collidable
. You probably would have a collision system taking care of this. But the point is, that this is all the car needs to know for the interaction to transpire. The ball then internally calculates the location of impact, it's own momentum, then does a lot of googlediegoop internally and shoots off to a better life. But the complicated stuff that made that happen is internal to the ball's behavior, and only the ball needs to know that. This segregation of internal vs external is called Encapsulation
. And the final pillar of object-oriented programming.
That's it, object-oriented programming has 3 fundamental principles, not 4.
Abstraction and Polymorphism are essentially the same! Sue me.
The Metaphor(s): Organs and Toys
Let us construct our Metaphor for this.
The Body. Human Body. To appease to my girlfriend. And to respect her privacy, we will choose any Human Body other than hers. I digress. I am sorry.
We have some opening in our body from which we introduce oxygen, food, waves, molecules and other chemical and physical phenomena. Those openings are our entry points.
And then we have other parts that process these to execute other functions. Organs. Now organs themselves are little machines. Humans which are Animals, which are Living Beings like a process come come to life (start), live (main loop) and die (end).
Why I chose this metaphor however is because of how object-oriented programming sets the boundaries towards the host system and where the core of the interaction happens. The human body resides in a physical world which also has chemical laws. What each organ does is determined by the most inner components, the simplest of buildings blocks. The cell, which implements the interaction with that system through chemical reactions. At the core of each organ microscopic interactions take place. This logic is encapsulated and isolated from other components in a hierarchical way through membranes, cell from cell, tissue from tissue, organ to organ and finally human to world. And there is only certain dedicated mechanisms that take place as interfaces between them. The body like an application also evolves and grows over time and when bugs are introduced, either your error prevention is good enough, aka you have good immune system, or your system will be sick.
The most wicked problems however, are design problems. Like cancer. A mutation deep inside one of the organs can take over and kill the entire system, if you let it grow. Also a cell by itself is pretty useless, it needs the full context of it's organ to be useful and distinguishable. So to make use of the function of an organ, even when only a small part is responsible for it, there is no other way other than to transplanting it.
Another metaphor I originally noted down, was that of toys. Toys on the most abstract level are just material: plastic, rubber, etc. Some designer had a certain purpose in mind intended for it, and defined a blueprint: a class. And the toy factory follows that blueprint to build the toy. The more features you add to it, the more affordances it gets: interfaces. An elastic part makes the toy bouncy for example, that allows the child to throw it or to collide it with other toys. However the older a child grows, very abstract and undefined toys become boring. The ball is replaced with an action figure. Something with very specific affordances. With lots of cool parts. However the more specific something get's the less composable it is. For example a lego brick on the flip side is very abstract. It has pegs on one side and anti-studs on the other. Other than that it is simply a rectangular brick of a certain color. Alone a lego brick is super boring. However it's degree of abstraction allow for it to be combined with other bricks, in almost any configuration. Giving unlimited possibilities. Similar to our body, which is also formed on the lowest level by very abstract chemical machines. However like objects these are contained by clear boundaries.
Functional programming
Again let us take a deeper look.
Lucky for you functional programming on it's surface has only two components: data and functions. But didn't we say that it is bad to only have these two? Yes. Now let's add the limitations of the paradigm.
There is no state!
Functions are not procedures, and they are not methods. They are functions in the mathematical sense. Mappings of one value to another. That sounds almost impossible to work with! How can that be useful?
First of all. Everything can still be modeled with data of any complexity. Think of it as objects without any functionality. Data can still contain other data, like objects. Data is then manipulated by functions. Data has types. These types are then in turn also composed of other types, until we reach basic types like numbers or boolean values. Now we have very simple functions that transform one type into another. And anywhere in your application where you interact with that type you can use that function. Your knowledge base is then composed of a network of transformations. What more, when your function returns a certain type in your application you can replace a value of that type with a call of that function. Functions just become very dynamic and extensible values. This idea that any value of a certain type in your application can be replaced by a function producing that type is called referential transparency
. And to be referentially transparent a function must be pure
. Like my girlfriend. There she is again. Hopefully I redeemed myself by now.
Pure means, the opposite of impure. But what is impure? Impure is any function that causes any effect outside of it's own boundaries, it's scope of execution
. Anything that interacts with an external system, or manipulates some shared mutable state. Mutation
, scary stuff. It can lead to cancer. As we have seen in our body metaphor.
Ok then but how do we access the engine of a car? In object-oriented programming we had encapsulation. I bump ball, and ball knows what to do. As we have seen in the previous section oo has a tendency of moving the fundamental interaction with the system to the core of objects: very hard to reach. Functional programming takes a different approach.
Remember how I said that functions are just values? We just pass the function that knows how to deal with engines, to another function that knows how to apply functions to engines inside of cars. A data type that defines such a function for itself that allows to apply function to an internal value is known as a Functor
(remember that for later). And functions that can process other functions as values known as higher-ordered
functions. This way the action applied to the internal of data is defined outside of the object, while another function provides the means to reach inside and navigate the data.
Our knowledge is defined in Modules
, that share the job of compartmentalizing our knowledge similar to classes. I like to think of one Module representing one Feature, or rather one sub-domain inside of our application, defining all the types and structure of data aswell as the collection of functions that can work with that data. But other than classes, all of these definitions are atomic. You can access any function individually. While in object-oriented programming you would need to pass around the Lung to attach oxygen to blood cells. Or if you were a good boy, and used composition, just the alveoli. We still have an object with all of it's properties irrelevant for our purpose that needs to be carried around for a single operation. Because the effect/the knowledge we are interested in is encapsulated inside the object! In functional programming you would instead first build the chemical domain, which tells you how a chemical is structured as data, and gives you all sorts of tools to manipulate one chemical into the other, or combine them into a compound chemical. And using this knowledge, build your process that attaches blood to oxygen in a new sub-domain. Let's call that Module Vitals. And whenever you need it, you just pass one oxygen and one blood cell to the function, and it joins them for you.
Having to define something as simple as joining oxygen to blood seems redundant if we had to redefine this operation for any two components that need to be joined. Functional programming also has several mechanisms of abstraction that would allow to express the operation on the property we are interested while ignoring the rest. The interface from object-oriented programming is replaced with a type class. Functor is one of these types, that given any internal type allows to apply a function to it. Abstraction. Polymorphism.
In summary while object-oriented programming makes you conceptualize the application as a collection of objects interfacing with each other, hiding the relevant processes and effects inside. Functional programming from the entrypoint is just the composition of many smaller building blocks forming this long chain of functions passing through values.
The Metaphor(s): Magic Wands and Onions
But enough of this half-hearted recycling of existing metaphors. Let's think of new Metaphor for the paradigm! For this one I chose a controversial one. A magic wand. I don't want to imply that functional programming is magical, and put it above object-oriented programming, even though it probably is. But because of the way I want to conceptualize it.
A wand is a very simple and unassuming object. It is static, and without the magic just a stick. This is comparable to the perceived limitation of stateless programming.
However with every movement of the wand we invoke a formula. The movement in this metaphor is one pure function. On it's own, meaningless. Just air. However let us imagine a magical (pure) space surrounding the wand in which each movement adds a property to a magic buffer and charges the wand. And then you add a movement before that movement. Or you compose one movement from two simpler movements. You end up with a chain of hierarchically composed movements. Since movements are so compatible with each other, they can just be combined indefinitely. And yet the magical effect starts to build up ever so powerful. Until reaching the tip and releasing the compound effect of all the movements.
If you want to correct one part of the spell -- you have a bug -- you pick out the one movement that you need to fix, and "train" that in isolation. And then you put it back together. To extend the metaphor further: From a very abstract mind space, where you have accumulated a deep knowledge, neatly arranged into their domain modules you pick out the effects you want to combine, look up their movement, string the movements together and externalize them into reality through the tip of the wand (the impure entry point).
The reason I chose this metaphor is because of the nature of the wand in relation to the movement. Movement is this composable abstract action which is the articulation of even more abstract and universal knowledge. While the wand is the object in reality, never changing, only when all the specifically composed movements are applied to it, something happens. The tip is the interface to reality. Some implicit mechanism separates the pure arcane energy from the effects that alter reality. But we as casters don't care. We are just happy that we can effortlessly piece together longer strings of movements. Replacing one piece with the other.
Another way to conceptualize functional programming applications is with an Onion. Read about it. For obvious reasons, I find the wand a more inspiring choice. The onion also implies a static object. One aspect that speaks for the onion is it's stratification from a pure core to multiple layers protected by an impure shell interacting with the outside world. This is what my wand metaphor misses. So we have to expand my metaphor one more time.
Instead of one wand, imagine an octopus, a contraption of wands where each tip is rooted in reality, in our messy impure external system. All the tips attached through arms to a central unit of knowledge. The smaller the tips (the impure parts) the more flexibility for the movement. The tips would then relay information in through movement, and out through movement. The same principles apply as with the single wand. I know, it sounds surreal. Like functional programming as a whole. And hey, it's not my fault; was functional programming easier to conceptualize we wouldn't have ended up with objects. But one can't choose thy family (looking at you, Inheritance!).
Takeaway
The main goal with these metaphors was to convey an intuitive picture for how paradigms define how knowledge is relayed into action, and where in the system the core knowledge resides and where in the system interactions take place.
In object-oriented programming interaction is internalized, through encapsulation you hide the messy bits from all the other components while providing clean interfaces. This lends itself to extensibility, gravitating towards adding more and more functions or methods to an existing "organ", but running the risk to create malicious mutations that spreads to other organs.
While functional programming clearly projects the messy interactions with the external systems to the exterior tips while remaining agile and flexible at the center. Chaining together movements that feed from a central source of knowledge.
I think as creative technologists, who mostly deal with object-oriented frameworks, we can benefit enormously from the ideas in functional programming. Being able to extend any creative algorithm at will by replacing what was just a simple value first with a whole string of new functions can create impressive outcomes, without breaking the program and aligns extremely well with how creative processes often unfold. Everything can be disassembled and reassembled. Pure functions work like lego pieces. Intuitively guiding affordances, of what can be combined. And makes the programming process extremely fun and liberating. Iterative and Expansive. Try it!