It's made me start thinking of programs more as multicellular organisms, or communities, where each part is responsible for a particular task. I think this is the core genius of OOP. It's not about classes or (Java) interfaces, it's about programs being communities of cohesive individuals with well-defined communication protocols.
edit to add: I think another key difference is in increasing the level of abstraction that you think about your program. You want to build a layer where you just say what should happen (the "declarative layer"), and keep that separate from the idea of how it should happen (the "implementation layer").
Low-level code correctness is overrated. With some experience it's trivial to achieve even in a crappy language like Java. Nothing except horrible syntax stops you from writing "functional" code in Java (and even that became easier in Java 8). This is what I and all sensible engineers I know are doing. In my experience, this "weak" version of FP plus a few tests is enough to virtually eliminate low-level coding bugs.
You can still get some surprising behaviors from 3d-party libraries and stronger FP can help with that a bit, but it's nowhere near as drastic of an improvement as many people present.
There are two big classes of errors I do often see in real life.
1. Spec omissions aka systems Broken As Designed. Your system works the way you expect it to work, but produces results users did not anticipate.
2. Emergent problems. Like someone shutting down your service for a day, causing queue backup, causing a huge batch of data being sent to vendor when the service is back online, causing vendor to throttle you, causing all other jobs being delayed.
#1 seems to be mostly a matter of experience and knowing when to show people some diagrams. OOP and especially agent-oriented programming can actually help you when reasoning about #2.
https://www.quora.com/Why-is-functional-programming-suited-f...
Functional programming is also easier to mathematically verify than imperative:
https://semantic-domain.blogspot.com/2018/04/are-functional-...
While you might not do proof, languages and methods that are easier for proof are often easier for automated analyses that look for problems, too. There's tools out there that can check code for concurrency problems that are extremely hard to spot with just testing. They might be easier to build or get more results if the program was functional/stateful instead of heavily stateful.
https://github.com/project-everest/hacl-star
You can verify stupid designs or smart ones. You can do impractical verification or pragmatic approaches. Formal verification is a tool whose outcomes depend on who wields it. The only consistent drawbacks it has are requiring more expertise, the project takes longer, and the project has to be simplified in design/implementation. High-quality software in general (eg OpenBSD) often has same properties, though. Possibly intrinsic to achieving correctness rather than additional requirement of formal methods.
Of course, you can still deadlock yourself; and read-modify-write can still be done in a racy fashion. Emergent properties of a system are still hard to reason about, but individual processes should be easy to reason about.
But... dynamic dispatch is a source of errors when it is very often possible to get the benefits via type classes, giving strong compile-time guarantees.
It's possible to understand "OOP is a higher-level paradigm than FP" in more than one way. I see no fundamental reason why you can't model the top-level interactions of a system using FP. And if state has been wrapped in a monad, you can chase the state through the codebase looking at the typeclasses inferred.
It's not higher-level, it just allows different types of abstraction. OOP is about procedural abstraction, ie. a set of procedures permits you to add new types, FP is about data abstraction, ie. a given set of data types permits you to add new procedures.
Most OOP languages are not pure OOP, so they permit some data abstraction as well, and FP languages with good module systems also permit procedural abstraction.
No it doesn't, not pure, typed OOP [1]. Unless you introduce dynamic types, then neither is more opinionated because you can just cast or dispatch at whim.
See: "Closures And Objects Are Equivalent" etc.
http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent
And
https://stackoverflow.com/questions/2497801/closures-are-poo...
To summarise, FP provides good reasoning in the small, and OO is needed in the large to orchestrate stateful services via protocols.
Re: there being no problem, that depends. Pure OOP has advantages and violating the properties often sacrifice some of those advantages.
That's exactly the assertion that I'd like substantiated. I don't think there's consensus on this. Sure, it's mentioned in the oft-cited "encapsulation, inheritance, polymorphism, abstraction" mantra - but so is inheritance, and yet prototype-based OO doesn't have it.
Personally, I would say that the only core principles of OOP are object identity (i.e. objects are distinguishable aside from their state), and polymorphism via dynamic dispatch. Everything else is optional.
There's also a conceptual argument: dynamic dispatch and object identity both require encapsulation. What do you think you're dispatching or comparing equality on if not hidden state?
Managing state is a central part of what FP systems do; a normal Haskell program is, in a sense, a function that returns an imperative program for handling stateful interaction. The stateful and stateless parts are clearly distinguished so that the problems associated with state can be managed more effectively and don't make it hard to reason about parts of the program that have no reason to be state-sensitive.
While the mechanisms are different, that separation seems broadly the point of most pure FP systems and many impure ones.
OOP, on the other hand, is clunky as a concept...
I don't have a strong opinion on the topic of OOP vs FP, but it's worth keeping in mind that people mean different things when they say OOP. Furthermore (and more importantly for this particular discussion), it also appears that people mean different things even when they specify they mean alan-kay/message-passing variant of OOP.
For many, it's something like Smalltalk and Ruby, with their very late binding and ability for objects to respond to messages even without matching methods. Others find Erlang and Elixir to be good embodiment of Alan Kay's vision. And the third groups seems to define it as "kind of like how bacteria communicate, and kind of like microservices", which is really too fuzzy for meaningful discussion.
In your link, Alan says: "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things."
That is... quite vague.
But you are not staying in your pure OO-world nowadays. There's always the boundary issue where you are giving up your careful encapsulation and where state needs to be transferred instead of being hidden away in some object (like exporting to JSON). Poof, your encapsulation is gone.
Furthermore, may OOP languages don't deal with ownership at all. If I'm a constructor and someone passes me an object A or worse, a list of As. Can I hold onto that list? Should I make a shallow copy? Should I make a deep copy, because I need the A objects in the state they are currently in?
What if I have to work with 3rd party libraries? What assumptions do they make about that?
Can I call methods on objects from multiple threads? I need to rely on the documentation to figure that out or provide synchronization myself just to make sure I'm not breaking things.
Also, it is no accident that refactoring tools have become so popular in the OO world. If you need to design OO with 'change' in mind, you need to employ lots and lots patterns that make your code looks like new FactoryBuilderPatternVisitorImpl().
But with refactoring, hey, I can easily change my classes thanks to my IDE, yay! Too bad if someone then passes me data that only fits the old classes and too bad if consumers of my library are getting nasty surprises.
It is with everything in software: use the appropriate tool for the job.
OOP != Java.
>OOP is great if all you are doing is OO and if your solution to a problem is running a simulation (what OOP was invented for).
Most problems most programmers solve are simulation in some sense. Including DevOps. I wish larger number of people realized this. Things would become much simpler.
The problem with class-oriented languages like Java is precisely that they are horrible for making simulations. Been there, done that.
>There's always the boundary issue where you are giving up your careful encapsulation and where state needs to be transferred instead of being hidden away in some object (like exporting to JSON). Poof, your encapsulation is gone.
Solving boundary issues is what OOP was designed for. Like, that's literally the original goal of the whole concept, as explained by Alan Kay many, many times.
You were able to send objects over the wire in Smalltalk in the 70s. Every time you open a webpage with JavaScript you're performing an ad-hoc reenactment of the same idea. The fat that this idea is implemented badly on the web doesn't mean this is a bad idea.
>Furthermore, may OOP languages don't deal with ownership at all. If I'm a constructor and someone passes me an object A or worse, a list of As. Can I hold onto that list? Should I make a shallow copy? Should I make a deep copy, because I need the A objects in the state they are currently in?
This problem has been solved in 1978.
http://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-20...
>Can I call methods on objects from multiple threads? I need to rely on the documentation to figure that out or provide synchronization myself just to make sure I'm not breaking things.
See the paper above.
BTW, guess how I found it? Alan Kay's talks.
>Too bad if someone then passes me data that only fits the old classes and too bad if consumers of my library are getting nasty surprises.
Again, getting rid of "raw data" was one of the core motivations behind OOP. See the email above.
I think I made it clear that OOP is fine if you can afford to live in that space. In practice you often can't.
Re the boundary problem: Sending objects over wire doesn't solve it at all, it makes it worse. Object identity for once and incompatible classes between client/server. Hiding data in magic objects just doesn't work. It's the reason CORBA failed, it's the reason RMI is a nightmare, it's the reason we have data-oriented wire protocols.
Works fine for billions of web pages that run JavaScript.
Actually, class-based handling of raw data can be done very nicely if you have multiple dispatch, which solves the so-called expression problem. Cf Julia.
I would really like to see some OOP language that implements James Reed's reference versioning or at lest a weaker implementation where every object "mutation" creates an effective copy with the changes, without touching anything that would be referenced previously. It would fix a lot of low-level annoyances people have with SmallTalk, JavaScript and so on. Would be extra amazing if it was based on prototypes, not classes. But at this point it sounds like I would have to write it myself.
Those structures don’t behave as objects themselves, but it’s not uncommon for Haskell monads, for example, to have very object-like behavior when you build them up a high layer, while each step in the monad is still an immutable value with cheap mutation-copies.
Are their any specific talks you recommend? I've only been exposed to OOP through Java so I'm curious to hear more about its roots
There is this famous talk from OOPSLA 1997:
https://www.youtube.com/watch?v=oKg1hTOQXoY
If you want to see what "practical" OOP looked like in the 80s, this is a good video:
What is OOP language?
And even for simulation-like code (or really any other case of "very-high-level" code featuring "object"-like entities linked by complex, non-trivial relationships) ECS systems work a lot better than plain old OOP, while preserving the wide variety of advantages that you rightly point out. Especially since ECS works as a reasonably-thin layer over the usual sort of data-oriented programming.
I used to believe that. I no longer do. Among other things, I watched this talk about Overwatch's architecture and it really seems like you have to go against your OO instincts to create larger simulations: https://www.youtube.com/watch?v=W3aieHjyNvw
OOP as actually implemented and found in the wild isn't about Smalltalk or message passing. It's about transforming functions into methods by unnecessarily wrapping behaviour in classes, as an example of cargo-cult programming.
OOP the idea is great. How do you actually find it in the world?
Take callback hell. Opaque callbacks are the functional equivalent of goto statements.
Also, many functional programmers, particularly Haskell programmers, seem not to care about readability and maintainability. They're focused on maximizing their own productivity as an individual coder, and writing cool concise and abstracted code that might not actually be easy for another engineer to understand and modify.
The difference though is that good functional programming is still quite common whereas good object oriented programming seems to be rare (the one exception being microservices).
in some sense they are worse because you can't just grep for the call site of an anonymous closure, like you can for a goto, it could be almost anywhere in the code base
Say what you want about the foreign-ness of the syntax, but Haskell code is some of the most maintainable code out there. I can often times dive into another person's haskell code and grok it more easily than the equivalent code in a "friendlier" language like python or ruby, nevermind a more comparable language like scala. Haskell is also a breeze to refactor, the compiler and type system make changing and moving arbitrary chunks of code around pretty straight-forward, even when the code is using abstractions you're not familiar with; something which cannot be said for many other languages. The biggest downside to haskell (apart from the learning curve) is having to deal with cabal+stack.
To use OOP properly is probably as complicated as doing functional programming or data-driven programming properly. The "problem" is that the learning curve is much less steep and the use-cases more extense, so obviously there's a lot more people misusing it.
A cousin comment notes that true objects are often called "actors". This is fine, but people who do this without an understanding of this history may come to miss out entirely on objects, only seeing what Erlang and Elm have to offer and not E.
As a hint, try removing the meme entirely from your inventory for a bit: For the next week, whenever you think or say "object-oriented", interrupt yourself and instead think about what specific language feature, be it classes or inheritance or whatever, you actually wanted to reference.
It works. It is a sufficiently good model for many domains, even though it obviously is "impure" by combining the domain model and generally a whole lot of implementation details nee requirements.
It clearly is an unreasonably effective strategy that has no solid theoretical footing, yet that does not prevent it from working in practice.
In terms of real programs in the real world, OOP in the form of Java/C++/C# is by far the most successful programming paradigm ever. It's sort of amazing that anyone would call it's track record 'abysmal'
Beyond that, failures all around. On top of my head:
* The insistence of designing systems fragmented in tiny state machines instead of systems processing immutable values passed around wholly and processed by pure functions. In the UI domain we finally got back to our senses with React.
* The insistence that values do not exist, but rather objects = data + code. Leading to brittle abominations like CORBA, agents, etc. For distributed systems, a humble RPC passing values [NOT objects] back and forth is infinitely more valuable.
* The insistence that inheritance is a useful design mechanism, leading to highly coupled codebases. Also, a prime cause for object-relational impedance mismatch.
* The insistence that one should overengineer code for extension without modification at every turn. How about just write the simplest version of the code for today, and refactor it if new requirements pop up.
* The insistence on messaging as fire-and-forget [aka goto] instead of structured function calls. Decades after we've learned why goto-oriented programming is a poor idea.
* The insistence on overusing polymorphism to mock everything in testing. Making it hard to trace how the codebase actually operates, and making it expensive to change the codebase because tests and code are tightly coupled.
* The insistence on overusing polymorphism for all type-based dispatch. Try writing a compiler in a purist OOP style, with a visitor for every type-based switch. Maybe it works in Smalltalk, 'cause in Java/C++/C# it gets boilerplaty really fast. Or even better, "encapsulate" your AST data into "objects hiding their internals" and do all type-based dispatch using vanilla polymorphism. Nothing screams "fun" like wading through tens of files to piece together what should have been a single switch block.
As for UIs, React is OOP. It borrows many ideas from FRP, but at its core are objects that have behaviour and state (components), and a system to orchestrate them. It might not be OO from 1990s Java, but it's definitely OO.
As for the rest, well:
* The concept of "values" form the basis for several important OOP design patterns - such as Value Objects and DTOs.
* Anything but very lightweight inheritance hasn't been in favour for many years, and most good OO developers favour composition.
* Writing the simplest version of code for today is a key tenet of TDD as practiced by several OOP leaders such as Bob Martin.
* GOTO is a permanent and nonreturnable change in program flow with little boundaries. Message passing is entirely different, very much bounded, and executed within control flows.
* Poorly designed tests are coupled heavily to code. Good tests are not. Test fakes can actually simplify debugging by removing and isolating large portions of the system.
* Most OO languages have a switch case that's perfectly acceptable to use. Many real-world applications that have never-ending spec changes (compilers work to a pretty limited and well-defined spec) benefit from using polymorphism to allow easier extensibility.
As a paradigm OOP encompasses a particularly wide spectrum, so while you can of course practice the worst OO has to offer in the form of deep inheritance hierarchies and overuse of design patterns "because we'll need them later", none of this is endemic to OO. In fact there's quite a happy medium combining OO with FP concepts (such as React) - or using OO at the boundaries to help assemble and mutate data before it gets passed to a core of pure functions.
As with everything, it's all about picking the right tool for the job, and getting the most out of the tools you are using. Steak knives and bread knives look almost exactly the same, but one is a lot better for tearing through meat.
Historical OOP orthodoxy took great lengths to contrast objects to values. For example, support for values in OOP mainstream languages has been tepid. With Java still failing to fully support value objects at the language level, project Lombok notwithstanding. With Python adding dataclasses support as late as 2017. There is a reason for decades of dragging the foot: OOP orthodoxy shuns values in favor of objects, defined as "identity, [...] state (data) and behavior (code)" [0]. Insisting on "objects" as the only right way to structure programming _is_ the historical failure of OOP. No amount of late attempts at co-opting other paradigms under the OOP umbrella is going to fix that.
I for one welcome the quiet morphing of OOP into VOP/FP. Makes life as a programmer sane again.
If you want to understand OOP idea do not think about Java/C# classes and inheritance. Think about OS processes/IPC or microservices. These are Objects. This is the basic idea behind OOP.
Getter/setter is the main anti-pattern of OOP design.
How so?
I think of OOP as a message passing paradigm. It's concerned with how messages are passed from one part of the system to another. It is not particularly concerned with how those messages are implemented under the hood. In that sense it's a higher level paradigm because OOP is all about building abstractions ("objects") and defining how they communicate, rather than wiring code together directly. FP doesn't really have anything to say about how you design your abstractions, just how you implement them in code.
Message passing can be used with a functional language (Erlang) or an imperative language (Go) to great effect. It can also be added onto (as a library) most other languages.
However, I want to know if the original author meant it that way. And I still don't see how message passing is a higher level paradigm than functional programming. It's an orthogonal paradigm, neither subsumes the other, and both benefit when used with the other.
But in practice, does it really make that much difference? A method call is really just a synchronous message. So at that point we're really talking about what messages objects should have - and looking at something like String, Array or Stream in Smalltalk, I don't see how it's substantially different from what we see in mainstream OO languages today.
There is an important distinction. Messages are interpreted, at runtime. Which means you can do certain kinds of metaprogramming without getting outside of the normal tools of the language.
This is not a black-and-white kind of thing, though. A method call can be more or less like a message depending on the language tooling provided.
https://www.baeldung.com/java-dynamic-proxies
C# also has something similar with TransparentProxy, not to mention "dynamic".
>And I still don't see how message passing is a higher level paradigm than functional programming.
Speaking practically, I see a lot of modern technologies that converge (very slowly) on the same ideas Alan Kay was talking about as OOP for the last 40 years. Micro-services, web pages with JavaScript, containers, etc. Those things are pretty high-level.
This seems to be Kay's key point. The description of object orientation is very unlike any modern system I've used, and he mentions elsewhere that even modern Smalltalks fail to live up to it.
My problem with this vision of OOP is that it would seem to take a great deal more support from the programming environment to make it work, compared to FP. Or perhaps the problem is that this "true" OOP is so much harder to grasp that it's impossible for me to approximate it in another language, where with FP I can obtain some of the benefits just by writing pure functions in an otherwise imperative language.
One way to think about this is that there isn't a difference between your programming environment and the rest of your computing environment. There are no real "applications". There are just message sends and objects floating around. This is what Smalltalk was/is like.
You know full well that those founding ideas are mostly irrelevant to what most people mean by "OOP" nowadays. "OOP" right now is C++, Java, C#, Python…
What Alan Kay was talking about is now called the "actor model", of which the most prominent champion is Erlang, which sells itself as a functional language.
Different times, different terms. Which is why I can say with a straight face that OOP sucks and Alan Kay is brilliant.
I am sick and tired of the arrogant, willfully ignorant developers touting their FP horn and bashing OOP without any qualifiers. This is getting as annoying as Java people posting their design pattern nonsense as if it was something good.
If you know what a monad is, you should also know the founding ideas behind OOP.
http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...
The ideas make sense. Moreover, Kay has an amazing track record of saying unpopular things and being proven right in the long run. If you spend some time looking into it, you should see that his high-level intuitions about software architecture are proving to be correct, despite many of his critics. A lot of hyped-up modern technologies are implicit, ad-hoc, overly complex implementations of OOP ideas done without realizing they are such. The whole IT industry would be far better off if people took time to look into and understand the original ideas behind OOP.
1. OOP is a higher-level paradigm than FP, so people comparing them directly usually are missing the point to begin with. OOP systems can be implemented in functional languages or use functional-flavored components.
2. It is beneficial to model algorithms without using state. FP is very good that that. (Boring but practical example: LINQ vs loops and variables in C#. Functional LINQ pretty much always "wins", just like Streams always win in Java.) However, large-scale systems almost always have state. If you do not have some wise approach for handling it, your designs will have issues at system level. OOP is one such approach and at that level its ideas have a lot of value.
I'll edit this post later to add a few more points.