7 ms·
OOP vs. Functional Programming (2014)
- d--b 8y agoplease add (2014)
- ruairidhwm 8y agoDone!
- d--b 8y agothx ;)
- lmm 8y ago> 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.
- danharaj 8y ago> The defining characteristic of OOP is that hidden state. Hidden state is the characteristic of abstraction, period. OOP has no special claim to it.
- lmm 8y agoNonsense. 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.
- thrmsforbfast 8y agoPerhaps 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.
- lmm 8y ago> 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.
- thrmsforbfast 8y ago"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.
- mlthoughts2018 8y agoThe 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.
- thrower123 8y agoI'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.
- dnautics 8y ago> 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.
- deleted 8y ago[deleted]
- deleted 8y ago[deleted]
- pka 8y ago> OO is not about state 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#, ...).
- millstone 8y agoThis 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.
- addicted 8y agoNot 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.
- kazinator 8y ago> 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.
- Animats 8y agoProgress 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.
- antidesitter 8y ago> 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.
- aikah 8y ago> 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.
- Animats 8y agoGo has "=" and ":=", to distinguish alteration of variables.
- jordache 8y agoI 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....
- thrmsforbfast 8y agoI 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".
- jpochtar 8y agoCan you help me understand the pullqoute? I didn’t understand what he’s talking about at all. Is it about not having to recompile?
- thrmsforbfast 8y agoSee https://en.wikipedia.org/wiki/Inversion_of_control https://en.wikipedia.org/wiki/Inversion_of_control and https://en.wikipedia.org/wiki/Dependency_inversion_principle https://en.wikipedia.org/wiki/Dependency_inversion_principle 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)
- TheCoelacanth 8y ago> 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.
- mcphage 8y agoI 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'.
- 08-15 8y agoThe 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.
- mcphage 8y agoSee, 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.
- Avi-D-coder 8y agoThe 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.
- joel_ms 8y agoAd-hoc polymorphism is interfaces in java, typeclasses in haskell, implicits in scala etc. Classes (usually) enable subtype polymorphism.
- Avi-D-coder 8y agoI 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.
- 08-15 8y agoThere's a 2018 update/rehash here: https://blog.cleancoder.com/uncle-bob/2018/04/13/FPvsOO.html 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.
- bcheung 8y agoI 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. 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.