> Objects are not data structures. Objects may use data structures; but the manner in which those data structures are used or contained is hidden. This is why data fields are private. From the outside looking in you cannot see any state. All you can see are functions. Therefore Objects are about functions not about state.
The statements are correct but the conclusion is backwards. The defining characteristic of OOP is that hidden state. Passing functions around is done all the time in functional languages.
> The bottom, bottom line here is simply this. OO programming is good, when you know what it is.
Ehhhh nope. The idea that you need "function pointers" to do polymorphism is confusing one particular implementation technique in one particular (frankly, bad) language for some universal rule. You can do polymorphism perfectly well without needing hidden mutable state - and when you do, life is generally better.
Nonsense. Most abstraction isn't about state at all (you won't find state anywhere in the definition of a group, say). You can of course abstract over the difference between having state and not having it (as OO does), but that's a virtually useless abstraction because it doesn't preserve any nontrivial rules/symmetries.
Perhaps a better way of saying is is that hidden data is a characteristic of abstraction. Which is certainly fair, and fits the analogy to algebra quite well.
But in any case, parent's comment that "OOP has no special claim" to hidden state/data (or abstraction) is sound; after all, ADTs already do that.
> Perhaps a better way of saying is is that hidden data is a characteristic of abstraction. Which is certainly fair, and fits the analogy to algebra quite well.
I'd disagree with even that much; I'd say abstraction is much more about parameterisation - factoring out the common structure from different constructions - than outright hiding anything.
> But in any case, parent's comment that "OOP has no special claim" to hidden state/data (or abstraction) is sound; after all, ADTs already do that.
ADTs don't hide mutable state. That much is something OO has a special claim to.
"hiding data" and "factoring out the common structure" are almost always intrinsically connected in software systems, because if you can poke at the data then you can violate contracts that give rise to the common structure.
This is particularly true of software, but I'd argue it's also true of basically every branch of mathematics outside of a few particular sub-fields of algebra (which are either extremely simple or extremely abstract, and often both).
> ADTs don't hide mutable state. That much is something OO has a special claim to.
How so? Abstract datatypes certainly hide mutable state. Just because a language doesn't have the "private" keyword doesn't mean that internal data can't be excluded from the interface... this was done all the time in e.g. C without the message passing semantics that characterize OO languages.
> Most abstraction isn't about state at all (you won't find state anywhere in the definition of a group, say).
Sure, but you also wouldn't care that π is defined as an equivalence class of cauchy sequences if you're treating (R, +) as a group. In fact, one does not care at all about the particular details of a group being a set-- one treats groups up to isomorphism.
But of course, I was talking about abstraction in programming.
> Sure, but you also wouldn't care that π is defined as an equivalence class of cauchy sequences if you're treating (R, +) as a group. In fact, one does not care at all about the particular details of a group being a set-- one treats groups up to isomorphism.
You don't care at that point, but you don't destroy the information either. You know that (R, +) forms a group and you might use that group structure to, say, efficiently sum a list of reals - but you know that the answer is a real and not just a "group element", and you would go back to doing real-specific operations on your total. So it's more about parameterisation than about outright hiding information.
> But of course, I was talking about abstraction in programming.
What's the difference? If you don't mean the normal kind of abstraction then what's the definition of the thing that you're talking about?
> You don't care at that point, but you don't destroy the information either. You know that (R, +) forms a group and you might use that group structure to, say, efficiently sum a list of reals - but you know that the answer is a real and not just a "group element", and you would go back to doing real-specific operations on your total. So it's more about parameterisation than about outright hiding information.
When you reason about something as a group, that reasoning operates with the details hidden. When you apply that abstract reasoning to a concrete problem, of course that information is still there. Abstraction is a boundary that looks different on either side. In a similar vein of your example, the compiler knows all the information you're hiding through your program abstractions, but from the point of view of pieces of your code, that abstraction has hidden something.
> What's the difference? If you don't mean the normal kind of abstraction then what's the definition of the thing that you're talking about?
The word abstraction is so... abstract that it isn't actually a uniform concept applied in different areas but instead many similar concepts that look the same if you squint.
The concept I'm talking about is abstraction in the context of program structure. This is more related to mathematical abstraction than a mere metaphor but it's not exactly the same thing as the abstraction in a definition like groups, nor is it the same abstract as in abstract art.
> When you reason about something as a group, that reasoning operates with the details hidden. When you apply that abstract reasoning to a concrete problem, of course that information is still there. Abstraction is a boundary that looks different on either side. In a similar vein of your example, the compiler knows all the information you're hiding through your program abstractions, but from the point of view of pieces of your code, that abstraction has hidden something.
If I write a function like:
f(x) = x + 2
would you say the value of x is "hidden" in the expression x + 2? I would say no: it's clear that there is a value of x at the point where we evaluate that expression. That's a different kind of thing from the OO style where we write o.f() and genuinely can't distinguish whether there is relevant internal state in o (and whether that state is mutated by the call to f()), or not.
There is a difference between a function `f` and the expression `x + 2` in your example. In fact, `f` as a function completely hides the expression from which it was defined. Usually functions are considered equal if they have the same values at every point. The upshot is, you can define a function from two different expressions and they can end up being the "same" function. This is indeed the hiding of some information.
In the composition of functions, f . g (x) = f(g(x)), that there is a result of g(x) is hidden by the composition, which only allows examination of the first input and the final output.
> There is a difference between a function `f` and the expression `x + 2` in your example. In fact, `f` as a function completely hides the expression from which it was defined. Usually functions are considered equal if they have the same values at every point. The upshot is, you can define a function from two different expressions and they can end up being the "same" function. This is indeed the hiding of some information.
It's hiding the differing constructions, but at that point I don't see that as a true difference. (\x . x + 2) . (\x . x + 2) seems to me to be the same value as \x . x + 4 in exactly the same way that 2 + 2 is the same value as 4.
> In the composition of functions, f . g (x) = f(g(x)), that there is a result of g(x) is hidden by the composition, which only allows examination of the first input and the final output.
> There is nothing special about OOP here.
Having mutable state hidden/encapsulated with a bundle of functions that may be entangled with that state is unique to OOP; as I said in the side thread, hidden structure is not the same as hidden state.
The article lost credibility with me for the “OO is not about state” section because it misses the point.
If all you have are singleton objects with static functions and no private data attributes, then I agree, but it’s also meaningless because such objects are very sincerely unrelated to anything from OOP. They are just stateless buckets of functions, which is more like a module than an object.
This is actually quite a good way to program in many cases. You can do it in Scala by only defining companion objects and never the actual class that it would correspond to (except for occasional cases where you need case classes or structural typing). You can do this in Python by just writing modules with no classes, and either use primitive data structures (list, set, dict, tuple, etc) for all your data, or else use absolutely minimal namedtuples.
I’d say this way of programming is almost totally FP and has hardly anything but superficial connections to OOP, which I think undermines the author’s point.
You can always claim it’s a semantic difference and different people mean different things with certain words. IMO that’d be disingenuous here. Any type of OOP that just uses objects as buckets of functions with no private data attributes is really just FP.
As soon as you add private data attributes whose values can break referential integrity of the object’s function calls, then it’s game over for FP and you’re in total OOP land.
I'm not sure I would call what you are describing FP, because I'm old enough to remember when it was procedural programming, and it was a very common pattern among people that didn't quite get the whole OO thing yet when the moved from C or Pascal to Java. The main difference, really, would be whether you just poke at globals, or thread them through.
> As soon as you add private data attributes whose values can break referential integrity of the object’s function calls function calls, then it’s game over for FP
Technically you can do something like this in fp land if you're using the actor model to encapsulate hidden state and erroneous code in a passed lambda does something strange to the hidden state. It's pretty hard to do since most fps with actor model put all of your interfaces to the state right in front of you.
Actors are very closely related to OOP, especially the Smalltalk/Self message-passing perspective. The main difference is that message sends to actors are asynchronous whilst objects usually aren't.
OO is very much about state, and that's the fundamental difference between non-FP languages and FP.
a1 = object.f(b);
a2 = object.f(b);
If a1 != a2 or f performs any side effects at all you've got state. Doesn't matter if it's "private" or "hidden". Your functions are not referentially transparent.
It also doesn't matter if your language implements OO in terms of message passing (Smalltalk, Erlang) or method calling (Java, C#, ...).
This is orthogonal. Many languages traditionally considered functional do not enforce referential transparency, and nothing prevents you from building referentially transparent functions in an OO language.
Not speaking specifically about this situation, but just because a language considered functional implements a certain behavior doesn’t mean the behavior is functional. The causation usually works the other way. It’s because a majority of the behaviors a language implements are functional that the language is considered functional.
As an example, you can write procedural code in Java, which is the poster child of OOP programming. That does not make the procedural code OOP.
> Objects are bags of functions, not bags of data.
That is just silly. I'd be willing to swallow the following revised version, in the context of immutable programming: "classes are bags of functions, not bags of data."
An object (class instance) is then just a datum which serves as a domain value to these functions. Given that domain value, each function in that bag-of-functions class produces a range value: and that bag of range values comprises the properties of the object.
Under traditional mutable OOP, those functions are impure in ways that logically require the object-datum to carry a representation of state somehow.
And anyway, since functions are data (being first class values), any bag of functions is necessarily a kind of bag of data.
Progress has come from making immutability more usable. Many of the troubles in programs come from something mutating state that something else is depending upon. This is not really a functional thing.
The important concept is "single assignment". This is where values are initialized at creation and never changed. Go, which is an imperative language, favors single assignment. Functional programming is a form of single assignment.
Single assignment in an imperative syntax has some advantages over a pure functional form. Values have names, which helps when reading code. You can use the same value twice. It's functional programming without the cult.
The downside of single assignment is that you're frantically creating and discarding values. Underneath, the machine is imperative. So you need compiler support for efficient creation and discarding of values. LISP spends a lot of time creating and discarding values, then garbage collecting them. So do Javascript, Python, etc. Vast amounts of effort have gone into making that efficient, and today it's not a big problem. Rust goes even further, handling most of that overhead at compile time.
> Single assignment in an imperative syntax has some advantages over a pure functional form. Values have names, which helps when reading code. You can use the same value twice.
This is where Haskell’s let <variable> = <expression> in <expression> bindings come in handy.
> Go, which is an imperative language, favors single assignment.
Go doesn't even have final variables. Go doesn't favor single assignment in anyway. Rust does as it is harder to create a mutable variable than an immutable one.
I think the point of the two styles not being mutually exclusive is helpful. So often, is the literature treating the two concept like either-or, very black and white....
I think the author does characterize a primary distinguishing feature of OOP.
That said, the post is a windy road with some dead-ends. Lots of statements that are highly contestable (see other top-level comments for examples).
So here is, IMO, the important take-away from the post on what's novel/unique/significant about the combination of abstraction/program design/programming techniques we call oop:
> In most software systems when one function calls another, the runtime dependency and the source code dependency point in the same direction. The calling module depends on the called module. However, when polymorphism is injected between the two there is an inversion of the source code dependency. The calling module still depends on the called module at run time. However, the source code of the calling module does not depend upon the source code of the called module. Rather both modules depend upon a polymorphic interface. This inversion allows the called module to act like a plugin.
This is a particular programming style. The author calls it "polymorphism", but that's far too large a label. The paragraph above is specific way of using polymorphism. This sort of programming style can be done in functional languages, but I think it's fair to say that 1) OOP did it first, and 2) OOP provides the most natural setting for thinking about what's going on in these sort of "plugin architectures".
> Indeed, the word “variable” is a misnomer in a functional language because you cannot vary them.
Variables in functional languages are variables in the sense of mathematical variables, i.e. they can take on multiple possible values. They don't need to change from one minute to the next to be true variables.
I do understand his initial frustration. Saying that FP languages solve the Strategy Pattern (say) with functions is correct, but so reductionist as to be useless. It's like saying you solved a problem using objects, or saying you solved a problem using monads. Sure, you absolutely can, but which monad you use is actually really important. There are a lot of things that fall under "The Strategy Pattern", and 'how you use functions to implement those' is just as big (and interesting!) a question as 'how you use objects to implement them'.
The Strategy Pattern comprises a single interface with a single method. In other words, the Strategy is a function, so it's entirely appropriate to say the FP equivalent of the Strategy Pattern is "function". Objects and interfaces just obscure that simple essence.
See, that’s what I mean about being reductionist. Is your answer wrong? Eh, not entirely, it’s just useless. Plenty of uses for the Strategy Pattern require more than just a function, and even those that a function is sufficient, what the function returns, and when, and why, matters. Reducing it down to a single function offers no understanding, it just sidesteps the point.
The statement author makes that polymorphism is belongs to OPP is incorrect.
Functional languages by design require better type systems that can accommodate more forms parametric polymorphism.
OPP traditionally encouraged the use of ad-hoc polymorphism (via classes) over parametric polymorphism (via generics). OPP languages generally also make it easy to perform Ad-hoc polymorphism at run-time with dynamic dispatch some languages even use dynamic dispatch by default.
TLDR: FP and OPP both have polymorphism.
FP encourages parametric polymorphism.
OPP encourages ad-hoc polymorphism.
I would argue that subtype polymorphism is functionally equivalent to Ad-hoc polymorphism especially in the presence of multiple inheritance.
The distinction is how polymorphic code is written not what the semantics can be expressed. E.g. rust traits allow for dynamic dispatch. While there is a distinction between ad-hoc polymorphism and subtype polymorphism, I don't believe it is necessary when comparing what semantics FP and OPP languages express.
Not that it adds any value, it's exactly as confused and pointless. Uncle Bob is getting old. He tries to find defining characteristics of OO, and ends up talking gibberish:
> The behavior [of o.f()] is dependent upon the type of o. i.e. f is polymorphic.
Nope, behavior of a "polymorphic" invocation depends on the value of the 'o' argument. (Types don't exist at runtime.) This happens all the time in FP, where passing around functions is normal.
> The mechanism of polymorphism must not create a source code dependency from the caller to the callee.
That's called a higher order function. You pass a function from module A to a HOF from module B. B ends up calling into A without a source code dependency. Big deal.
> What is FP?
> referential transparency
Lispers would object. My take on it is that the essence of FP is higher order functions. If you don't have them, you need Design Patterns instead. If you do have HOFs, you don't see why anyone would bother with OO.
Bottom line: OO doesn't exist. It's just pointless jargon that only exists to make a difference without a distinction (e.g. between Smalltalk and Scheme), and possibly to sell books and Clean Code seminars.
I normally like what "Uncle Bob" has to say but I do not agree with a lot of what he says in the article.
> Objects are bags of functions, not bags of data.
That might be an ideal case, but it is not reality. Pretty much every OO language has both of them combined.
> Is there really so much difference between f(o), o.f(), and (f o)? Are we really saying that the difference is just about the syntax of a function call?
Yes, there is.
Try doing a compose function with the OOP / member expression syntax.
Syntax is the structure behind mental representations and mental representations have tremendous power in expanding the physical limits of thought in the brain.
> The overriding difference between a functional language and a non-functional language is that functional languages don’t have assignment statements.
The overriding difference between FP and OOP languages is that the unit of composition and abstraction is the function or object, respectively.
44 comments
[ 10.7 ms ] story [ 94.2 ms ] threadThe statements are correct but the conclusion is backwards. The defining characteristic of OOP is that hidden state. Passing functions around is done all the time in functional languages.
> The bottom, bottom line here is simply this. OO programming is good, when you know what it is.
Ehhhh nope. The idea that you need "function pointers" to do polymorphism is confusing one particular implementation technique in one particular (frankly, bad) language for some universal rule. You can do polymorphism perfectly well without needing hidden mutable state - and when you do, life is generally better.
Hidden state is the characteristic of abstraction, period. OOP has no special claim to it.
But in any case, parent's comment that "OOP has no special claim" to hidden state/data (or abstraction) is sound; after all, ADTs already do that.
I'd disagree with even that much; I'd say abstraction is much more about parameterisation - factoring out the common structure from different constructions - than outright hiding anything.
> But in any case, parent's comment that "OOP has no special claim" to hidden state/data (or abstraction) is sound; after all, ADTs already do that.
ADTs don't hide mutable state. That much is something OO has a special claim to.
This is particularly true of software, but I'd argue it's also true of basically every branch of mathematics outside of a few particular sub-fields of algebra (which are either extremely simple or extremely abstract, and often both).
> ADTs don't hide mutable state. That much is something OO has a special claim to.
How so? Abstract datatypes certainly hide mutable state. Just because a language doesn't have the "private" keyword doesn't mean that internal data can't be excluded from the interface... this was done all the time in e.g. C without the message passing semantics that characterize OO languages.
Maybe if you can mutate it, but having it be visible isn't an issue.
> Abstract datatypes certainly hide mutable state.
ADTs are normally understood to be values i.e. immutable. Hidden structure is not the same thing as hidden state.
Sure, but you also wouldn't care that π is defined as an equivalence class of cauchy sequences if you're treating (R, +) as a group. In fact, one does not care at all about the particular details of a group being a set-- one treats groups up to isomorphism.
But of course, I was talking about abstraction in programming.
You don't care at that point, but you don't destroy the information either. You know that (R, +) forms a group and you might use that group structure to, say, efficiently sum a list of reals - but you know that the answer is a real and not just a "group element", and you would go back to doing real-specific operations on your total. So it's more about parameterisation than about outright hiding information.
> But of course, I was talking about abstraction in programming.
What's the difference? If you don't mean the normal kind of abstraction then what's the definition of the thing that you're talking about?
When you reason about something as a group, that reasoning operates with the details hidden. When you apply that abstract reasoning to a concrete problem, of course that information is still there. Abstraction is a boundary that looks different on either side. In a similar vein of your example, the compiler knows all the information you're hiding through your program abstractions, but from the point of view of pieces of your code, that abstraction has hidden something.
> What's the difference? If you don't mean the normal kind of abstraction then what's the definition of the thing that you're talking about?
The word abstraction is so... abstract that it isn't actually a uniform concept applied in different areas but instead many similar concepts that look the same if you squint.
The concept I'm talking about is abstraction in the context of program structure. This is more related to mathematical abstraction than a mere metaphor but it's not exactly the same thing as the abstraction in a definition like groups, nor is it the same abstract as in abstract art.
If I write a function like:
would you say the value of x is "hidden" in the expression x + 2? I would say no: it's clear that there is a value of x at the point where we evaluate that expression. That's a different kind of thing from the OO style where we write o.f() and genuinely can't distinguish whether there is relevant internal state in o (and whether that state is mutated by the call to f()), or not.In the composition of functions, f . g (x) = f(g(x)), that there is a result of g(x) is hidden by the composition, which only allows examination of the first input and the final output.
There is nothing special about OOP here.
It's hiding the differing constructions, but at that point I don't see that as a true difference. (\x . x + 2) . (\x . x + 2) seems to me to be the same value as \x . x + 4 in exactly the same way that 2 + 2 is the same value as 4.
> In the composition of functions, f . g (x) = f(g(x)), that there is a result of g(x) is hidden by the composition, which only allows examination of the first input and the final output.
> There is nothing special about OOP here.
Having mutable state hidden/encapsulated with a bundle of functions that may be entangled with that state is unique to OOP; as I said in the side thread, hidden structure is not the same as hidden state.
If all you have are singleton objects with static functions and no private data attributes, then I agree, but it’s also meaningless because such objects are very sincerely unrelated to anything from OOP. They are just stateless buckets of functions, which is more like a module than an object.
This is actually quite a good way to program in many cases. You can do it in Scala by only defining companion objects and never the actual class that it would correspond to (except for occasional cases where you need case classes or structural typing). You can do this in Python by just writing modules with no classes, and either use primitive data structures (list, set, dict, tuple, etc) for all your data, or else use absolutely minimal namedtuples.
I’d say this way of programming is almost totally FP and has hardly anything but superficial connections to OOP, which I think undermines the author’s point.
You can always claim it’s a semantic difference and different people mean different things with certain words. IMO that’d be disingenuous here. Any type of OOP that just uses objects as buckets of functions with no private data attributes is really just FP.
As soon as you add private data attributes whose values can break referential integrity of the object’s function calls, then it’s game over for FP and you’re in total OOP land.
Technically you can do something like this in fp land if you're using the actor model to encapsulate hidden state and erroneous code in a passed lambda does something strange to the hidden state. It's pretty hard to do since most fps with actor model put all of your interfaces to the state right in front of you.
OO is very much about state, and that's the fundamental difference between non-FP languages and FP.
If a1 != a2 or f performs any side effects at all you've got state. Doesn't matter if it's "private" or "hidden". Your functions are not referentially transparent.It also doesn't matter if your language implements OO in terms of message passing (Smalltalk, Erlang) or method calling (Java, C#, ...).
As an example, you can write procedural code in Java, which is the poster child of OOP programming. That does not make the procedural code OOP.
That is just silly. I'd be willing to swallow the following revised version, in the context of immutable programming: "classes are bags of functions, not bags of data."
An object (class instance) is then just a datum which serves as a domain value to these functions. Given that domain value, each function in that bag-of-functions class produces a range value: and that bag of range values comprises the properties of the object.
Under traditional mutable OOP, those functions are impure in ways that logically require the object-datum to carry a representation of state somehow.
And anyway, since functions are data (being first class values), any bag of functions is necessarily a kind of bag of data.
The important concept is "single assignment". This is where values are initialized at creation and never changed. Go, which is an imperative language, favors single assignment. Functional programming is a form of single assignment.
Single assignment in an imperative syntax has some advantages over a pure functional form. Values have names, which helps when reading code. You can use the same value twice. It's functional programming without the cult.
The downside of single assignment is that you're frantically creating and discarding values. Underneath, the machine is imperative. So you need compiler support for efficient creation and discarding of values. LISP spends a lot of time creating and discarding values, then garbage collecting them. So do Javascript, Python, etc. Vast amounts of effort have gone into making that efficient, and today it's not a big problem. Rust goes even further, handling most of that overhead at compile time.
This is where Haskell’s let <variable> = <expression> in <expression> bindings come in handy.
Go doesn't even have final variables. Go doesn't favor single assignment in anyway. Rust does as it is harder to create a mutable variable than an immutable one.
That said, the post is a windy road with some dead-ends. Lots of statements that are highly contestable (see other top-level comments for examples).
So here is, IMO, the important take-away from the post on what's novel/unique/significant about the combination of abstraction/program design/programming techniques we call oop:
> In most software systems when one function calls another, the runtime dependency and the source code dependency point in the same direction. The calling module depends on the called module. However, when polymorphism is injected between the two there is an inversion of the source code dependency. The calling module still depends on the called module at run time. However, the source code of the calling module does not depend upon the source code of the called module. Rather both modules depend upon a polymorphic interface. This inversion allows the called module to act like a plugin.
This is a particular programming style. The author calls it "polymorphism", but that's far too large a label. The paragraph above is specific way of using polymorphism. This sort of programming style can be done in functional languages, but I think it's fair to say that 1) OOP did it first, and 2) OOP provides the most natural setting for thinking about what's going on in these sort of "plugin architectures".
And don't overthink it. You've probably seen this a million times in your work life. The description is indeed not great.
It's not always about not having to recompile, but can be (see eg dependency injection)
Variables in functional languages are variables in the sense of mathematical variables, i.e. they can take on multiple possible values. They don't need to change from one minute to the next to be true variables.
Functional languages by design require better type systems that can accommodate more forms parametric polymorphism.
OPP traditionally encouraged the use of ad-hoc polymorphism (via classes) over parametric polymorphism (via generics). OPP languages generally also make it easy to perform Ad-hoc polymorphism at run-time with dynamic dispatch some languages even use dynamic dispatch by default.
TLDR: FP and OPP both have polymorphism. FP encourages parametric polymorphism. OPP encourages ad-hoc polymorphism.
The distinction is how polymorphic code is written not what the semantics can be expressed. E.g. rust traits allow for dynamic dispatch. While there is a distinction between ad-hoc polymorphism and subtype polymorphism, I don't believe it is necessary when comparing what semantics FP and OPP languages express.
https://blog.cleancoder.com/uncle-bob/2018/04/13/FPvsOO.html
Not that it adds any value, it's exactly as confused and pointless. Uncle Bob is getting old. He tries to find defining characteristics of OO, and ends up talking gibberish:
> The behavior [of o.f()] is dependent upon the type of o. i.e. f is polymorphic.
Nope, behavior of a "polymorphic" invocation depends on the value of the 'o' argument. (Types don't exist at runtime.) This happens all the time in FP, where passing around functions is normal.
> The mechanism of polymorphism must not create a source code dependency from the caller to the callee.
That's called a higher order function. You pass a function from module A to a HOF from module B. B ends up calling into A without a source code dependency. Big deal.
> What is FP? > referential transparency
Lispers would object. My take on it is that the essence of FP is higher order functions. If you don't have them, you need Design Patterns instead. If you do have HOFs, you don't see why anyone would bother with OO.
Bottom line: OO doesn't exist. It's just pointless jargon that only exists to make a difference without a distinction (e.g. between Smalltalk and Scheme), and possibly to sell books and Clean Code seminars.
> Objects are bags of functions, not bags of data.
That might be an ideal case, but it is not reality. Pretty much every OO language has both of them combined.
> Is there really so much difference between f(o), o.f(), and (f o)? Are we really saying that the difference is just about the syntax of a function call?
Yes, there is.
Try doing a compose function with the OOP / member expression syntax.
Here it is in JS using FP syntax:
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))
It would be even shorter in Haskell.
Try doing long division with Roman numerals.
Syntax is the structure behind mental representations and mental representations have tremendous power in expanding the physical limits of thought in the brain.
> The overriding difference between a functional language and a non-functional language is that functional languages don’t have assignment statements.
The overriding difference between FP and OOP languages is that the unit of composition and abstraction is the function or object, respectively.