loading...
Cover image for Function vs Object

Function vs Object

stereobooster profile image stereobooster Originally published at stereobooster.com on ・4 min read

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.

-- Closure conversion: How to compile lambda

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 sequence s1 → s2 → s3 → · · · of states. The step (si, si+1) represents a transition from state si to state si+1.
  • State-Action Behavior. A step is a triple (s, α, ti), where s and t are states and α is an action. A state-action behavior is a sequence s1 -α1→ s2 -α2→ s3 -α3→ · · ·. The step (si, αi, si+1) represents a transition from state si to state si+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

Posted on by:

stereobooster profile

stereobooster

@stereobooster

Hello, I'm a full stack web developer. Follow me on Twitter!

Discussion

markdown guide
 

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.)

 

Functional Programming is not just made up of functions, but of pure functions.

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.

Closures aren't pure FP.

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)

When I say state, I mean data. I'm not saying this data will change over time. Maybe this is what confuses people?

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"):

const addSome = (x) => (y) => x + y;
const addFive = addSome(5);
addFive(1) === addFive(1) 

Closure with re-assignment += ("not pure"):

const counter = (x) => (y) => x += y;
const countFromZero = counter(0);
countFromZero(1) !== countFromZero(1)

My point is that there is nothing wrong with closures. It is re-assignment and mutation which make closures (or functions) "not pure".

Re state: in pure FP I think we mean a "named" piece of data whose instances get changed over time:

go :: Int -> Int
go 0 = 0
go s = s + go (s-1)

Here s can be thought of as state, even though specific bindings of s don't change.

We observe state over the course of computation.

 

Closures aren't pure FP.

Lambda calculus has functions with free variables. Here's the Haskell definition of a closure:

wiki.haskell.org/Closure

 

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.

 

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)

Not sure what you mean, even the Wikipedia entry on closures relates them to free variables in LC:

en.m.wikipedia.org/wiki/Closure_(c...

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)

 

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.:

const bark = name => {
  console.log(`${name} is a good dog!`); // Dirty side effect
  return name;
}

Bark in DogFunction becomes a map function:

const DogFunction = name => {
  return {
    map: f => {
      return DogFunction(f(name));
    }
  }
}

Then we call it like this:

const dog = DogFunction("good boy");
const dogAfterBark = dog.map(bark);
// logs "good boy is a good dog!" and return a new dog

This is how I see the example with a dog.

Edit: added code parsing

 

The function returns an object.

This is JS specifics, I don't have other key-value data structure

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.


"bark" is a function with a side effect

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:

class DogClass {
  #color = 'white';
  #needToWash = false;
  walk(isRaining = false) {
    this.#color = isRaining || this.#color === 'dirty' ? 'dirty' : this.#color;
    this.#needToWash = this.#needToWash || isRaining;
  }
}
const belka = new DogClass();
belka.walk(); // "belka" mutated: { color: 'white', needToWash: false }
belka.walk(true); // "belka" mutated: { color: 'dirty', needToWash: true }
belka.walk(); // "belka" mutated: { color: 'dirty', needToWash: true }

FP way based on the example:

const DogFunction = dog => ({
  map: f => DogFunction(f(dog));
});
const walk = isRaining => dog => ({
  ...dog, // if there are some other fields
  color: isRaining || dog.color === 'dirty' ? 'dirty' : dog.color,
  needToWash: dog.needToWash || isRaining,
});
const belka = DogFunction({ color: 'white', needToWash: false });
const belkaAfterWalking = belka
  .map(walk())
  .map(walk(true))
  .map(walk());
// belka didn't mutate: { color: 'white', needToWash: false }
// belkaAfterWalking: { color: 'dirty', needToWash: true }

DogFunction can have a "walk" method but it is more OOP way. Compare how different it is comparing to the previous more abstract example:

const DogFunction = dog => ({
  walk: isRaining => DogFunction({
    ...dog, // if there are some other fields
    color: isRaining || dog.color === 'dirty' ? 'dirty' : dog.color,
    needToWash: dog.needToWash || isRaining,
  }),
const belkaAfterWalking = belka
  .walk()
  .walk(true)
  .walk();
// belka didn't mutate: { color: 'white', needToWash: false }
// belkaAfterWalking: { color: 'dirty', needToWash: true }
});

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

The question which we haven't touched is a mutation.

 

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).

1) How do you model and use in FP relations between domain "things", like orders, clients, suppliers, goods, invoices, organizational units, etc.?

There are different approaches, but I guess simple data structures would work - lists, trees, graphs? The question is too broad.

2) How do you handle in FP graphical user interfaces with widgets?

Broad question. It can be interpreted in different ways, for example, take a look at how they do it in Elm. ¯\_(ツ)_/¯

3) Are there easy to follow FP frameworks based on MVC patterns, for example?

In FP they don't use design patterns. Read the next post in the series for details.

 

1) How do you model and use in FP relations between domain "things", like orders, clients, suppliers, goods, invoices, organizational units, etc.?

There are different approaches, but I guess simple data structures would work - lists, trees, graphs? The question is too broad.

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?

2) How do you handle in FP graphical user interfaces with widgets?

Broad question. It can be interpreted in different ways, for example, take a look at how they do it in Elm.

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?

Then is it me, or is this similar to procedural programming with ADT (abstract data types)?

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.

If the latter, then there are problems with scale of systems.

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).

The bigger the system grows, the more functions are introduced in global namespace, or possibly in nested namespaces.

What the difference between namespacing with objects and namespacing with modules?

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.

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.

To the point where it might blend with math and formal proof of correctness.

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.

Any other real-world examples?

I don't know. Search the internet for examples. Haskell, F#, Scala, Kotlin.

From this perspective, how much time does one have to invest to understand Functional Programming approach?

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.)

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?

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).

What [is] the difference between namespacing with objects and namespacing with modules?

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 ;)

You always get the same output for the same input, which is not always true for objects. [emphasis added]

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.

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.)

Very true. Just a different set of concepts. By the way, combination of map and reduce 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 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:

"I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages (so messaging came at the very beginning -- it took a while to see how to do messaging in a programming language efficiently enough to be useful)...

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things...

But just to show how stubbornly an idea can hang on, all through the seventies and eighties, there were many people who tried to get by with "Remote Procedure Call" instead of thinking about objects and messages."

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:

I wanted to get rid of data.

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:

I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea. The big idea is "messaging".

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:

Sending a message to an actor is entirely free of side effects such as those in the message mechanisms of the current SMALL TALK machine of Alan Kay and the port mechanism of Krutat and Balzer.

— A Universal Modular ACTOR Formalism for Artificial Intelligence, Carl Hewitt et al.

 

I like that first article you linked 👍

This stuck out to me:

This means that you can do things like send messages to objects written in different languages, transmit object definitions by mail, send messages via SMS, etc. In my opinion this is where a lot of the power of “message-passing” lies, but it’s also one of the least explored aspects.

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, 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...

 

Sorry to say man but it is all terribly wrong, objects are not state with attached behaviors, they are holding a state but not state itself. Functional programming is against manipulating state, it is declarative. In FP "they" don't like to pass function, it is not a key principle but just a result of principles of FP. You have started with wrong assumptions and make totally wrong conclusions, however it was curious to read.

 

Ok, not sure where to start here...

  1. Would you care to write your post with the right conclusions?
  2. "objects are not state with attached behaviors, they are holding a state but not state itself" would you care to explain it or provide the reference?
  3. "Functional programming is against manipulating state, it is declarative". Never said otherwise.
  4. "In FP they don't like to pass function..." full phrase reads as "they like to pass functions as values", opposed to most OOP (old generation) languages, which doesn't support functions as values, and instead they pass objects.
  5. Adding "Sorry to say" to salty phrase doesn't make it more respectful. Next time change the phrasing as well (or don't add "sorry to say"). The respectful versions would sound, for example, "It can happen that you arrived at wrong conclusions. Here is how I see the situation... <explanation>"