There is an ongoing discussion about the difference between object-oriented programming (OOP) and functional programming (FP). Let's talk about similarities instead. Let's talk about the main building blocks: functions and objects.
If I won't be lazy this is gonna be a series of posts.
What is an object?
I tried to find a good definition, but it was harder than I thought a lot of sources talking about what is OOP, but nobody bothers to explain what is an object.
Let's go with object definition from Java, I guess:
Objects are key to understanding object-oriented technology. Look around right now and you'll find many examples of real-world objects: your dog, your desk, your television set, your bicycle.
Real-world objects share two characteristics: They all have state and behavior. Dogs have state (name, color, breed, hungry) and behavior (barking, fetching, wagging tail). Bicycles also have state (current gear, current pedal cadence, current speed) and behavior (changing gear, changing pedal cadence, applying brakes). Identifying the state and behavior for real-world objects is a great way to begin thinking in terms of object-oriented programming.
Pretty approachable definition. I will rephrase it a bit. The object is a state with a behavior attached to it.
What is a function?
I wrote 2 posts about it:
Let's go with the simplified definition (in the same vein as the object definition) and say that function is a behavior (for precise definition see links above).
In functional programming, they like to pass functions as values, to be able to do this functions "converted" to closures (converted is not a precise word here, because closure is a function with free variables, but let's go with a simplified view).
What is closure (in programming language)?
Closures are data structures with both a code and a data component.
I will rephrase it a bit. Closure (or function as value) is a behavior with a state attached to it. (State, in this case, is immutable. I refer to any data as a state)
Wait a second 🤔
Compare those 2 definitions again:
- The object is a state with a behavior attached to it
- The closure (or function as value) is a behavior with a state attached to it
Aren't they the same?
I don't believe it. What is your proof?
Let's write some codes. I will use JavaScript because it supports both paradigms.
class DogClass {
#name;
constructor(name) {
this.#name = name;
}
bark() {
return `${this.#name} is a good dog!`;
}
}
const belka = new DogClass('Belka');
belka.bark();
Note: this example uses "Class field declarations for JavaScript" proposal to declare private field name. At the moment of posting example works in Chrome.
const DogFunction = (name) => {
return {
bark: () => {
return `${name} is a good dog!`;
}
}
}
const strelka = DogFunction('Strelka');
strelka.bark();
Note: function returns record data structure (which in JS confusingly named "Object", but we don't use any "objecty" feature we use it as a simple key-value data structure). Variable name
privately stored in the scope of a closure, there is no way to access it outside.
Not a new idea
If you think about it makes a lot of sense: all computers deal with state (data) and behavior. This idea was discovered again and again:
Here is how Lamport defines computation:
There are several ways to define computation. For now, I take the simplest: a computation is a sequence of steps, which I call a behavior. There are three common choices for what a step is, leading to three different kinds of behavior:
- Action Behavior. A step is an action, which is just an element of some set of actions. An action behavior is a sequence of actions.
- State Behavior. A step is a pair
(s, t)
of states, where a state is an element of some set of states. A state behavior is a sequences1 → s2 → s3 → · · ·
of states. The step(si, si+1)
represents a transition from statesi
to statesi+1
.- State-Action Behavior. A step is a triple
(s, α, ti)
, wheres
andt
are states andα
is an action. A state-action behavior is a sequences1 -α1→ s2 -α2→ s3 -α3→ · · ·
. The step(si, αi, si+1)
represents a transition from statesi
to statesi+1
that is performed by actionαi
.-- Computation and State Machines. Leslie Lamport, 19 April 2008
Wirth wrote the book "Algorithms + Data Structures = Programs".
Ray Toal wrote about types: A type consists of a set of values and a set of allowable operations.
PS
The question which we haven't touched is a mutation. In "pure" FP, mutations are not allowed. In OOP they are allowed. When I say pure I mean lambda calculus with lazy evaluation and IO monad, which is a narrow area ¯\_(ツ)_/¯
.
Photo by NordWood Themes on Unsplash
Top comments (34)
Functional Programming is not just made up of functions, but of pure functions. You've already started to touch on this, but these are the most important traits of a pure function according to FP:
No side-effects. Accepts input, returns output, nothing more. (You mentioned this.)
No state within a function; the same input should always yield the same output on each call. (Exceptions exist...sorta? Closures aren't pure FP.)
The implementation of a function has absolutely no effect on any other part of the code. As long as the input and output are the same, the details don't matter to the rest of the program.
The function does exactly one thing; it should call other functions to handle any other things.
That said, I have a feeling you're going to go into all this later? (At least, I hope you will.)
I don't want to touch the whole surface, I would need to explain, lazy evaluation, side effects, IO monad (I'm not ready for that one). So I keep it simple for now.
Why not? Closures with mutation and re-assignment are not pure FP, but otherwise...
Closures have state, which the purists say you should avoid.
But I'm not a purist.
It's not that "state", which they avoid in FP (unless you add mutation and reassignment). You can think of it as variable binding
Yes, I know. It appears there are two camps on this. Clearly, the other factor is implementation, anyway.
As for myself, I'm content to say "frankly, my dear, I don't give a grep." ;-P
I have no idea about the second camp. Any Haskell (the most wide spreaded pure FP language) programmer would agree that closures are functional. When I say state, I mean data. I'm not saying this data will change over time. Maybe this is what confuses people? Consts - e.g. state which you can set only once and never change after are functional. Variable binding is functional as well.
(There is a link to Haskell wiki in the thread)
It could be. FP is far newer a concept to me than OOP, so I won't rule out missing something!
Go figure this happens while I'm writing an entire chapter on functional programming in Python. At least this gives me information for putting some of said confusion to rest.
Thanks for the feedback. Interesting is there some kind of authoritative definition for state in this case? My PoV is that whenever you need to allocate or write to memory this is a state, even if you never change it after the first write.
From merriam-webster: a condition or stage in the physical being of something.
For example, let's imagine pure FP program which is not doing side effects, but only doing calculation of some big number. We can pause in the middle of calculation (put computer in a sleep mode) and resume later. The fact that we were able to resume means we have state (right?).
But on the other side a lot of people would consider only mutable data as state...
From what I understood, the main complaint against stateful closures (would that be what you call them) is when you provide it with the same inputs, and get different outputs. Any function should provide the same output for the same input, every time.
But, as you pointed out, constants don't contribute that issue.
Closure without mutation or re-assignment ("pure"):
Closure with re-assignment
+=
("not pure"):My point is that there is nothing wrong with closures. It is re-assignment and mutation which make closures (or functions) "not pure".
Yup, makes sense!
Re state: in pure FP I think we mean a "named" piece of data whose instances get changed over time:
Here
s
can be thought of as state, even though specific bindings ofs
don't change.We observe state over the course of computation.
OOP has hammers, functional programming has the ability to smash things.
Advocates of OOP, like having specific things to smash with. Advocates of functional programming think just being able to smash is enough because they aren’t throwing thunderbolts, like hammers also can do.
When it comes down to it, if we measure by smash, both Hulk and Thor do it quite well.
It really just comes down to the style of smashing you like to do.
mjölnir.smash() or smash()
OOP: mjölnir.smash()
// mjölnir is an object
FP: mjölnir.smash()
// mjölnir is a namespace
In FP functions without arguments are code smell because most probable they contain side effects. So in FP it would be:
const mjölnirSmash = smash(mjölnir);
const newGround = mjölnirSmash(currentGround);
Or with ADT something like this:
const ground = Functor(/* some data */);
const newGround = ground.map(smash(mjölnir));
From Nidavellir import mjölnir as stormbreaker
Stormbreaker.smash()
Well, the FP example looks the same because it is, actually, almost the same. The function returns an object. "bark" is a function with a side effect. In FP world this kind of function would be executed in some IO structure. At least it should return something, e.g.:
Bark in DogFunction becomes a map function:
Then we call it like this:
This is how I see the example with a dog.
Edit: added code parsing
This is JS specifics, I don't have other key-value data structure
This was for the demo purpose, changed it to return.
It is better now w/o a side effect, however, the difference comes with adding more logic, e.g. changing an attribute of it.
Classic OOP way:
FP way based on the example:
DogFunction can have a "walk" method but it is more OOP way. Compare how different it is comparing to the previous more abstract example:
Still no mutations in between. FP and OOP can be similar only in very simple examples when mutations haven't joined the game.
Yes I mentioned this in the post as well
I think it's an even more interesting question to ask "What were objects intended to be in the first place?"
Alan Kay is considered one of the fathers of OOP. Here's his take on what he intended objects to be:
So, while it's true that objects can be viewed as are merely state + behaviour, that was not necessarily the original intent.
Kay also said:
In other words, he didn't want objects to be passing data to each other (directly). He wanted objects to send messages to each other - and the messages themselves might happen to have data inside them, if needed.
In another place, Kay said:
For those interested, I'd highly recommend looking into the actor model. I think this represents a closer realization of what Kay intended.
On a larger scale, something like event-driven microservices is also within this line of thinking.
I plan this as a series of posts. At some point I want to write posts "What is OOP?" and "What is FP?". The problem with the definition of OOP is that there is no single right definition. I consider 3 main sources (which have equal rights): Simula (Ole-Johan Dahl, Kristen Nygaard), C++ (Bjarne Stroustrup), SmallTalk (Alan Kay).
I need some time to gather full picture, meantime you can consult with those sources:
Interpretation of OOP as
The big idea is "messaging"
is pretty close to the idea of actor model:I like that first article you linked 👍
This stuck out to me:
I would agree that it's a messy topic. OOP gets a bad wrap for easily producing messy code. In my experience, using messaging patterns and isolating code properly can avoid this.
There is such a thing as messy and hard-to-understand FP code too 🙃
There is messy OOP code too.
There is well-structured FP. And well-structured OOP.
I enjoyed your article for pointing this out 👌
Yes it uses free variables. My question is
closure ≡ open lambda term
(open lambda term - the one with free variables)? Because from implementation point of view closed lambda terms can be closures as well.(This is just some thoughts out loud. Not questioning your comment)
Ok, just three silly questions.
1) How do you model and use in FP relations between domain "things", like orders, clients, suppliers, goods, invoices, organizational units, etc.?
2) How do you handle in FP graphical user interfaces with widgets?
3) Are there easy to follow FP frameworks based on MVC patterns, for example?
This is kind of out of the scope of the post. I didn't try to compare the whole FP vs OOP. I simply presented a small slice of FP and OOP that has similarities (closures and objects as value).
There are different approaches, but I guess simple data structures would work - lists, trees, graphs? The question is too broad.
Broad question. It can be interpreted in different ways, for example, take a look at how they do it in Elm.
¯\_(ツ)_/¯
In FP they don't use design patterns. Read the next post in the series for details.
Ok then, perfectly understood: simple data structures and set of functions (possibly pure functions in FP). Then is it me, or is this similar to procedural programming with ADT (abstract data types)? Say, a C or Pascal program with user-defined data types that match domain concepts?
If the latter, then there are problems with scale of systems. The bigger the system grows, the more functions are introduced in global namespace, or possibly in nested namespaces. Given that, in OOP languages there are not only namespaces (or modules), but classes that are typically used to encapsulate and solve sub-problems.
Good example is a class that implements some calculation algorithm: pass parameters to constructor, store them in private fields and call one or more public methods to get the result. Self-contained piece of code, that can be easily unit-tested, inherited and extended. No need to pass many parameters from one function to another.
But again, in FP the notion of a function went way futher than in other imperative languages. To the point where it might blend with math and formal proof of correctness. Is this the "true way" (TM) to follow from now on?
I know Elm and I really like it :) Any other real-world examples?
In object frameworks like: C#/XAML/MVVM, Delphi/VCL, Delphi/FireMonkey, C++/Qt, JS/React, TS/Angular - there is a notion of model and data binding. Such frameworks are relatively easy to understand, use and extend. And the knowledge of one of these can be directly or indirectly applied to other OOP languages / frameworks.
From this perspective, how much time does one have to invest to understand Functional Programming approach? And what are the benefits?
Finally here is my fourth simple question:
4) Is Functional Programming suited well enough to rewrite existing programs (websites, mobile apps, Line-Of-Business apps, etc.) using this paradigm, or are there areas where FP seems to suit best?
In the real world, a lot of time people would mix paradigms, because separation in paradigms is arbitrary in the first place (this is not a science, this is closer to the art). For example, see this article about if, or for example, you can use
.map
in the OOP-ish language.The big difference (at least one which first comes to my mind) with procedural programming is "functions as values". Which doesn't seem like a lot, but allows to do a lot of neat tricks. If you add lazy evaluation it will allow to do even more tricks (pure FP needs lazy evaluation, in Lisp they don't have it by default, but they have macros). This is a broad area it would take me N posts to cover. To show all the tricks and why it matters.
Why? Do you have any empirical evidence for this? I know people deliver 100k codebases to production (LoC is not the same as "scale", but we don't have another fair measure for this).
What the difference between namespacing with objects and namespacing with modules?
I don't know how to respond to this. What can be more unit testable than pure function? You always get the same output for the same input, which is not always true for objects. As well in FP they typically use plain data structures, so you don't need a special mocking library.
There is a difference between proving the correctness of the program and the formalism of FP. This is an urban legend that FP programs are formally proved to be correct right away. No, they are not. You need to write additional verification for it, the same way as you would do for any other paradigm. See this post for details.
I don't know. Search the internet for examples. Haskell, F#, Scala, Kotlin.
There are patterns in FP as well, which you can learn and then reuse across languages. For example,
map
,reduce
, functions as values, monads (option-monad, either-monad, futures - similar to promises, but not the same, etc.)FP the same way as OOP as procedural style is general programming paradigms (for example, logic programming is not general), so they suitable almost for anything (95% of use-cases ?), most of the time is just a matter of taste. There are some edge cases for which you need something else, for example, I would not use FP languages (as far as I know most of them are garbage collected) to write Memcached (key-value storage). To write key-value storage you need not a garbage-collected language or non-stop garbage collection as they have in Pony.
Regarding "scale of systems" I was mostly thinking about number of names (be it functions, classes, etc.) that might collide with one another. Namespaces of course solve part of the problem, in the meaning that functions have some "prefixes" before their names (like name of namespace or module for example).
I didn't give an example here, sorry. I had in mind Java 9 modules, and upcoming C++20 modules. They solve different problems than just namespaces in these languages.
Regarding testability. I was thinking rather about a set of functions (methods) enclosed in a class. It's rare to do everything in just one function, typically one function uses other functions to express the result. I was never trying to imply that functional programs are difficult to be unit-tested ;)
Now I don't know how to respond to this... Seems like a serious bug to me. There's not a problem to create objects with immutable state, where the results of method calls are always the same. Plus such immutable objects can easily be shared between threads. It depends on the style and/or necessity.
Very true. Just a different set of concepts. By the way, combination of
map
andreduce
is one of my favourites (!) I learned long ago.Thank you very much for your time and interesting discussion. I will keep coming to your blog and occasionally ask some more questions :)
I tried to find the source for this definition (which I remembered myself, but wasn't sure where I got it from, probably from exactly this wiki). Interestingly nothing else refers open lambda term as closure. In every source that I met closures are treated as implementation detail of lambda terms🤔.
(I agree that closures are "functional", this is lambda term + environment)
Basically, from what I've understood, a closure is still functional, but according to some purists, it's not "pure functional" because it has state.
Not that I really prioritize "purity" in terms of functional programming. Avoiding state is good for the most part, but at some point it becomes relatively impractical. Common sense required.
Yes, I love this insight, also known as: "Objects are a poor man's closures!"
For example, for better or for worse, d3 uses a lot of this (closure state instead of objects) in their API. See e.g. github.com/d3/d3-scale/blob/master...
Some comments may only be visible to logged-in visitors. Sign in to view all comments.