Why is OOP still so widely spread?
stackoverflow.blog
stackoverflow.blog
What happens once something reaches the top is that you start to have people with lots of experience in it that are becoming more and more aware of some of the small warts. And they start to heavily complain about them.
The people with less experience listen to those complaints, but don't have the understanding to the nuances and minutiae involved to really understand what the problem is and how big a problem it is, if it means the entire paradigm is garbage, or just some details that can still be improved on, and other small things where there might be better ways to approach it.
So, the less experienced start to think it must just suck. And they don't want to spend their effort learning something with so many issues that clearly some of the smartest devs say has tons of problems and there are better approaches for.
This eventually leads to the cultish flame wars and all that which we see way too often when discussing technicalities. The problem being, most people do not actually understand what is bad or good and the trade offs at play, thus when they debate, they instead pray on their affiliation and beliefs of what they've read but did not properly understand.
You can see this on display in virtually every hackernews thread. Somebody will say something like "I sort of like X, but my problem with it is Y". But what they don't tell you is their experience level, the context of how they came to believe Y, or really any supporting understanding of whether Y is an actual technical tradeoff that needs to be considered. It's sort of an overly grandiose, performative way of saying "oh yeah I tried haskell once, and I got stuck on this thing, so I never came back to it."
As one of the novices just trying to allocate my time, I'm constantly trying to understand whether, say, it's worth really understanding C++ at a deep level, or whether I should just try to learn Rust. All too often, I find this devolves into me scouring the internet for "C++ is ok but the ownership model is so primitive that I would never start a new project in it" and comparing it to "My problem with Rust is that it's still such a nascent language I can't trust it to have the libraries I need". Who are these people, and why are they saying these things? Are their gripes relevant to me? I have no way to know!
Reading some comments on hackernews, I often come up on a comment that seems to be speaking another language. At first I'm intimidated into thinking that there's a secret club of intellectuals that understand exactly what this comment is talking about. But then I start to wonder.. is this actually elite minds speaking in a very high-level way with extremely advanced context? Or, is it just some overly opinionated junior person who just assumes everybody is working on the same web tech stack they are, and speaks with that implicit assumption? When they say "OOP is now considered an anti-pattern, most companies are moving towards reactive FP", do they really mean "I work at snapchat and the ads group I work for is moving away from OOP. My friend at amazon said he's doing the same thing"?
Yes, thankyou! I think about this every time I see "Systems are so powerful we can just throw RAM/CPU cycles at every problem, efficiency doesn't matter..." as if everyone on the planet is doing web apps, and there are no engineering/architectural situations that might drive certain languages or design principles. Such as this:
https://www.intel.com/content/dam/www/programmable/us/en/pdf...
I guess I'm just focused on a niche market...
I love HN don’t get me wrong, but my coworkers and I like to poke fun at it. Some jokes are “why didn’t you just use rust?” and “Well what did Paul Graham have to say about it?” when debating something. It’s a culture like any other. I would say a lot of super smart people are here, who have deep knowledge on the most esoteric niche subjects, and provide a lot of sage wisdom and advice. But there are a lot of cranks too (including myself!).
I think this forum attracts a non-trivial number of people who have a hyperfocus issue beginning with the letter A.
It's why grammar nazis are the most insufferable, because their picking apart of your sentences appears to invalidate your argument, when really they're just being pricks.
In many HN threads with lots of comments, at some point in the thread (and very often starting at or near the top), the comments successively veer off to the right, sometimes to a ridiculous extent, forcing rightmost comments into a thin vertical column that is a pain to read. And worse, the main topic of the OP is often not discussed much at all, since the majority of comments are about these minute inconsequential details, as you say above - and since these trivia subthreads tend to get upvoted the most.
I've been programming since I was maybe 13, and being 34 today I find this to be more and more true. Programming IS JUST A TOOL to get something done. When I say "just a tool", I do not mean to ~undermine OR overexcite~ the concept of a tool. The real power to seek as a programmer is knowing lots of programming platforms and paradigms, as then you have the knowledge to select the best tool or technique to accomplish solving a specific problem within a domain at hand.
Sometimes that 'best tool' really just happens to be the one I have X years of experience in, because I'm here, right now, in front of the project, and have solved the same problem Y times before with acceptable results (systems running, people getting value as expected). That doesn't automatically justify being a stick-in-the-mud and never adopting any new technology ever.
But it also means that jumping to new tools on a semi-annual basis, you'll have a hard time having justified confidence you can deliver something of value. I have to qualify with "justified" because... I've seen too many folks who have confidence and... really shouldn't. And I recognize that because I can see how I was once there.
I had a call from someone in 2017 asking about something I'd built for them in 2002. System was still running. Someone had been doing basic system upgrades every so often, but they'd hit some logical issues and needed more insight, and pinged me. Last year I noticed a previous client project started in 2000 was still up. I've no doubt they've updated some of the back end stuff, but some of the stuff I put in place I can still see (html comment, semi-unusual url structure, etc).
Understanding that what you build might actually be in place, working for people for 15-20 years is hard to get your head around until you've actually done this for 20 years. When I was 20 I could not understand the ramifications of decisions impacting people or companies for 20 years - I just didn't have that life experience - no one at 20 does. We're often fed magical press releases about acquisitions and exits and we read stories of things shutting down, getting sunset, etc. Those aren't the norms though, I don't think.
With that in mind, my criteria for choosing the 'right tool' has shifted some in the last few years, and I'm always a bit more hesitant to jump on latest shiny trends, as... I do get somewhat concerned about the long term viability. That has to be a factor in 'best tool' calculations, no? (sorry for long ramble)...
The problem is it takes years to learn one tool, and it take years to see one case as screws, another as nails, etc.
EDIT: OTOH, as you gain more experience, you can classify problem better and look or even develop tools that solve that particular problem. Which is why angular / react / vue is developed and popular.
Somewhat. You can't become an expert in a matter of months, but by the same token, my PHP and Java skills didn't get demonstrably better between years 8 and years 9 of experience.
Other things outside the language got better. One was my ability to understand problems and potential solutions (from both technical and business/operations perspectives). Another was the ecosystems around those languages. Best practices, documentation and tooling all evolved to deal with new needs, new hardware, etc. The ability to adapt to those changes and understand the role of the core language/tech in addressing problems is key. Figuring out which new tools (or language features, perhaps) will bring the most value to you and your projects is as much as a skill as learning the language itself - you'll never know every single aspect of everything, so learn to accept that.
> Figuring out which new tools (or language features, perhaps) will bring the most value to you and your projects is as much as a skill as learning the language itself
Exactly in my view, programming language such as PHP and Java isn't tool, they are a set of tools. However the toolset are different between each programming language, let's say that in PHP the hammer has larger head than Java.
Let's say that programming features such as iteration (for / while loop) and conditional expression (if) are tools, that can be used to solve specific problems.
Well sorry if the analogy or metaphor isn't really accurate...
That helps for 'solo' stuff. Trying to introduce those ideas and techniques in existing teams is almost always more difficult than it should be.
That's pretty miserable, really. At least it pays reasonably well.
This seems like sage advice, especially in a time where people have become so focused on the developer experience and the software lifecycle. But for the sake of conversation, can I fish for an area of disagreement?
I notice that the WGID principle often gets hijacked into the strong version - let's call it the "Software is Just A Craft" conjecture. This version says that we've basically got software all figured out, and from here on out, everything is just different flavors of the same process. C for this, Python for that - it's just up to the craftsman to choose his tools wisely. To me, this kind of fatalism seems like the death of programming as a technology field. I don't know if it's a lack of imagination, or just getting too comfortable as a practitioner of "the craft", but some people seem to relish the idea that there can be no progress, only practice.
As a concrete example, consider Python's inability to use native threads. I see some people talk about this as a tradeoff, as if you're trading the performance of C for the convenience of Python in a zero-sum game. But that's just an approximation. Python _could_ have native support for threads if it was designed better. You could leave everything about its syntax alone, but just allow threading, if only the time was put into engineer it that way. Of course now, it's too late - we have too many legacy systems and too much code relying on how it was already built. But this is not the same as it being in a fundamental trade space. Python is just missing a feature, and now we know better.
It strikes me, then, that the level-headed "choose the right tool for the job" advice is really more just a way to be pragmatic in the here-and-now of writing software. You're not going to write a new operating system, language and ecosystem by the end of this week, so you have to realistically assess your options and grade them on their strengths and weaknesses. I just want to make the point that this is not core to the nature of programming languages and paradigms, just from wanting to build useful things on a schedule. But it has to be possible to make things strictly more expressive, strictly more performant, strictly easier to debug, strictly more portable, strictly... better?
Try not to get bogged down by what others say. If you’re looking for a job in a particular industry, follow the fads of that industry. Otherwise, just keep building your knowledge and worry less about whether it’s the right specific technology.
Think about languages as tools: focus on the tools you need to use. Your career will be long and there will be plenty of time to branch out, but you have very limited time for mastery, so you should master the tools you use often.
All too often, I find this devolves into me scouring the internet for "C++ is ok but the ownership model is so primitive that I would never start a new project in it" and comparing it to "My problem with Rust is that it's still such a nascent language I can't trust it to have the libraries I need". Who are these people, and why are they saying these things? Are their gripes relevant to me? I have no way to know!
Honestly, just pick one and run with it. Part of becoming an experienced programmer is developing your own heuristics for how to pick the right tool for the right job and how to read this type of discussion, and a big part of that is building up both wins and losses to train your intuition.
When they say "OOP is now considered an anti-pattern, most companies are moving towards reactive FP", do they really mean "I work at snapchat and the ads group I work for is moving away from OOP. My friend at amazon said he's doing the same thing"?
If it's an internet forum and you don't see references or citations, it's probably safe to assume it's the latter. They might still be right, but not verifiably so.
Is there a consensus one way or the other? Not only no, but $PROFANITY no. The person who claims that there is... yeah, consider them a blowhard who is out of touch with reality.
Lot's of FP "fans" just writing procedural code with a terrible mess of functions and no architecture as well. Quite the mixed bag. But they're very passionate and believe in talking about FP as the one true way. I suppose most things go through a phase like that though.
But if you've been around long enough, you don't get too jazzed about any one thing.
You don't start a moderate-sized project! You start a small project and then grow it to become moderate.
There are some other more subtle issues.
This article goes in depth into some less well known ones related to data structure efficiency.
https://jaxenter.com/disadvantages-of-purely-functional-prog...
The insistence on immutability comes from FP being closer to mathematics. But, computers aren't actually abstract equation solving machines. They are machines that spend much of their time copying data between mutable storage cells of various kinds. So, a lot of very important and common algorithms require mutability and don't have natural, efficient immutable equivalents. But FP requires you to be immutable, so, you can end up paying a heavy efficiency tax. Not many pure FP game engines out there!
Even when there are problems that are theoretically pure functions, like a compiler, real world compilers like LLVM or Graal are not functional, because you give up too much performance by doing that. Functional problems still benefit from not gratuitously copying data all over the place, mutating graphs in place, etc. Cache locality is too powerful an optimisation to give up.
OOP is fundamentally a paradigm about modelling a mutable world. It turns out that most software isn't implementing a pure function but rather a reduced model of reality, so, that makes it highly appropriate. Almost any database for example is a mutable model of reality (even if a fictional one like in a game). OOP languages have weak mutability controls, but fundamentally, a mutable variable is more "powerful" in some sense than an immutable one.
FP languages are often rather poor on things that OOP emphasises heavily, like encapsulation. FP gives you data structures and functions. Namespaces, usually. Beyond that it gets ropey. The core insight of OOP is that you often want to bind code to data very strongly, because that lets you really control the scope of functions much more easily. This in turn makes it easier to scale up teams, as people can more easily own their little corner of the world. OOP languages like Java have a lot of visibility control features, and are still getting more even in recent years.
Inheritance (subtyping) gets shit on a lot these days by the kids, but it's a very natural paradigm to model certain kinds of problems, for example it makes a lot of sense for GUI toolkits. Game engines also get a lot of good mileage out of it. Pure composition using layers is less effective, partly because it translates to worse machine code. Virtual method dispatch is highly optimised and thus a relatively efficient way to share and customise code. Interface dispatch is less efficient and proxying calls to composed inner objects, even less so again.
FP languages often end up trying to bolt on features to the functional paradigm that are natural fits in imperative OOP-oriented languages. For example, FP languages handle errors with a Maybe type and monads. OOP languages can do this too, but they can also do exceptions which are often a more natural way to handle errors, with various benefits. Strictly this is an imperative vs FP thing not an OOP vs FP thing.
There are some other issues that are less fundamental. FP languages are part of a family tree, and for whatever reason that family tree loves obscure syntax with lots of operator overloading. There are many OOP languages that are designed by industrial designers for usability in large teams, so they restrict how much of a mess you can make. FP languages are generally research languages that have little usage in industry, so FP code can often end up being very hard to read with a lot of bizarre custom operators. You could make a clear, easy to read and abuse-resistant FP language but nobody does.
Many interesting points in this comment, but I am not sure about the mutable part of this assertion. I see those two concepts often presented as fundamentally intricated but modelisation is an effort of abstraction not tied to implementation choices, and objects can be perfectly immutable.
Using these words, I would simply say OOP is fundamentally a coding paradigm about modelling a system, nothing more. And that is why it has been so successful.
Using OOP and avoiding mutability when appropriate, I would say you get most of FP benefits without abandoning the modelisation capabilities and objects features.
And more and more FP constructs are now integrated in OOP languages. Then APIs can become more important than the language itself.
That's what have been demonstrated by modern Java in my opinion.
It's really not (and I'm as one of the most outspoken critics of laziness in Haskell).
So it is kind of strange that nowadays FP == anything Haskell like.
Likewise I learned OOP via Turbo Pascal 5.5, followed up with frameworks like Turbo Vision and OWL, picked C++, Smalltalk and plenty of other OOP languages.
So it also feels ironic that nowadays OOP == anything Java like.
Sayeth the graybeard.
Basically, this has ensured that I do not accept truisms about how everything is kinda similar - it isn't, and that has real world consequences. In the case of OOP it's generally the consequence of boring you out of existence since it is so inextricably linked to Java at this point.
I'll grant you that you can solve problems with any language, though. ;-)
There's another dimension of the issue: types of the project the person has experience with.
Something that works in a low-level Windows desktop doesn't necessarily work in high-level Linux web. Something that works for NASA or SpaceX doesn't necessarily work for a mobile game startup. Something that works for FAANG is not necessarily the best way for a web site that's running on a single server. Something awesome for a social network is not guaranteed to do any good for a bank, or a research lab.
That "something" can be pretty much anything: languages, libraries, IDEs, frameworks, project management practices, hiring practices. The only thing these projects have in common is "people need to write code that does something". Almost everything else is different. And yet we have many discussions where people working in different industries, on projects with different requirements, budget, users, revenue sources are arguing which language is better.
It's not even that -> there is not 'hate' but among a fairly small group.
There is no general movement among the rank and file devs against OOP, nor among leaders.
It's really just a specific group.
More to you point - all incumbent systems have 'arbitrary antagonist'. It doesn't matter who is in charge, who is #1, who is the best, there will always be vocal critics.
When in truth they’ve simply formed an echo chamber for their own loudly-stated views.
I think it's lazy to dismiss the OOP as hate from experts who are complaining. I think a lot of criticism for OOP comes from experts who used to program in OOP, often with decades of experience, and have moved on to other paradigms, and are proudly proclaiming "I will never go back". If you want a really good (non-individual) example, take React, yes, the framework as a whole, which has quietly migrated away from OOP to promoting FP style as best practice; and hell, this is inside of a programming language that while proclaiming to be multi-paradigm, was very much created with Object Orientation in mind (yes, I am aware that it was supposed to be a lisp at one point, but almost everything in the language is Object object).
I think there's good evidence that this isn't just a run of the mill "grass is greener" grousing by experts. Here's a challenge. Can you name a contemporary expert or a platform that started or spent a good chunk of time as FP and ran back towards OOP (I would say since 1990 or so, as it is a thing that FPs had very poor performance before that era)?
I can't think of a time where we killed a project or rewrote a project to make it more functional. Or where OOP bite us.
React went functional for a reason. Does everyone share that reason?
I’m not sure. For example, frameworks are often nicer in OO than FP. Of course some wag will suggest you shouldn’t use frameworks, only libraries, but that misses the point.
Plugins strike me as another use case that works better in OO than FP. I’m certainly not saying you can’t have a plugin architecture in FP though, it just won’t be as nice to use, that’s all.
I expect in a few years the OO revival will be strong. “Look, we can neatly model fundamental concepts such as timers again!”
>> Can you name
Does es5 to es6 count? es5 was in theory already OO but the predominant usage appeared to be more heavily influenced by FP. I could be wrong on that but books like javascript the good parts were popular.
There are some very nice FP frameworks, for example Phoenix LiveView, or phoenix in general. Elixir has lots of mini-frameworks (like the Enum and Access protocols). They're a joy to work with and pretty easy to understand. I'm not really sure what constitutes a plugin, but I've written what I would consider "plugins" for the BEAM vm (loaded at runtime, with an exposed API behaviour called into by the system), and they're pretty smooth and easy to handle.
Arguably all of the BEAM OTP (std lib) and the patterns that packages expose for building persistent services are FP frameworks. If I may be permitted to make a more apples-to-apples comparison between the Erlang BEAM framework, to the BeOS C++ framework (both highly concurrent message-passing, actor frameworks). While I loved programming for BeOS, IMO doing everything functionally is far far smoother an experience than doing everything with objects. Both I enjoyed far more than programming the Dalvik (android) Java framework (though that was circa 2009).
Anyways having experience in all four quadrants, I don't think it's as simple as ("frameworks/plugins -> OOP; libraries -> FP).
Math.abs(-5)
That's a library call.A framework is like django or rails. You have to structure your code to fit within django's paradigm, while a library is just utilities you can use in your own code...
2. Hollywood principle. Library: you call us. Framework: we call you.
3. Frameworks are good for novices in certain paradigm because they guide along a standardised way to build apps. If you however find the framework's way not precise enough or its abstraction leaky then you will have a hard time escaping from the straightjacket.
Lot of frameworks actually are a combination of two things:
1. the actual "way of building apps"
2. useful library of functions
Lot of people would like to start with 1 and use 2 to guide them in the beginning, but drop 1 later on. Unfortunately frameworks couple 1 and 2 together very tightly making many people not even consider such frameworks.
> Lot of frameworks actually are a combination of two things:
> 1. the actual "way of building apps" > 2. useful library of functions
This describes React pretty much spot on. And yet, React people are very determined to point out that it's React is a library, not a framework.
That's normally what I would call a framework.
Now I don't know, maybe there's ways to use React only as a library, not sure if it would provide any benefit in that way.
It's also hard to argue that Whatsapp hasn't been wildly successful at scaling to a billion users on a miniscule engineering team in spite of "impracticality of FP".
You've made explicit another confounding factor: a language is more than its syntax - you have to consider the entire ecosystem and constraints being operated under - including tooling and libraries
My company has 200 Java engineers, and a dozen systems written in Java; it's not practical to migrate everything to <FP language>.
Spring Boot solves so many things out of the box that I'd have to implement myself in <FP language>, which isn't practical.
FP has a lot of features but it's not practical to maintain a large complex codebase using those features.
React is probably not a good example, because it didn't really expose its users to the horrors of OOP. There wasn't much inheritance and subclassing going on in the userland; you didn't really see SecondaryButton component inherit from the Button component and override its "render" or "onClick" method. True, there were some such codebases, but it was extremely rare. So no, I don't think React users have ever experienced the full extent of OOP, where you have to hunt through your codebase up the hierarchy chain for the bit of code that is misbehaving.
I'm sorry if this is what you understood. The hate is from people hearing the complaints of experts. The experts generally have great understanding of the pros and cons, and are aware of the weaknesses, which they will openly talk about and sometimes that will make it sound worse, almost hyperbolic to others. Even I'm faulty of doing this, I might say this just sucks, or it's total rubbish, but really I mean it's pretty good and there's a lot of good ideas and parts, just needs some refinement and a few modifications because there are still problems with it, and I just faced one such wart so I'm frustrated and writing a big blog post to complain about it.
And this is the source of the misunderstanding. Overreaction and hyperboles from experts, taken at face value to those who are learning about it.
To your other points, I think it's interesting you bring up React since its biggest competitors: Vue, Svelte and Angular are all OO based and seem to be doing well.
> Can you name a contemporary expert or a platform that started or spent a good chunk of time as FP and ran back towards OOP
Hum, I think most did. OCaml added an object system to ML. CommonLisp added an object system to lisp. Haskell added TypeClasses. Scala is an attempt at an Haskell like language with a full object system. F# and Kotlin both have object systems as well. One could argue actors are a purer form of OO. And so on.
See, my point is that experts only ever complained about certain aspects of current iteration of so called OO programming languages. And as you can see, alternate languages they'd reach for or newer languages after them (or even newer version of those languages) all tried to address those shortcomings, but did not throw the baby out with the bathwater. There is convergence of ideas, and most of those languages have many if not most of the OO staple feature set, while having taken a few out or added some other inspired often by FP, but sometimes by other paradigms too.
You get people hearing that OOP sucks, and they start to have fears and hatred towards it, thinking that all OO and every single idea and concept related to it is bad.
This is called stereotyping, and it's not productive to bring into a technical conversation. Even inside FP it exists, ever saw a Scheme programer and a Haskell programmer debate? They'd go on forever about whose the one true Functional Programmer.
Haskell type classes have nothing to do with OOP in the least, and as far as I know, Ocaml is a counterexample rather than an example to your point: they included OOP becsuse it was all the hype at the time of conception, but the OOP features are really quite unpopular today.
As you were completely off with Haskell, I can't exactly trust that you're right about any of the other languages "running back to OOP". If anything it seems to me the languages that started out as multi-paradigm FP/OOP have become more purely FP over time, as OOP has failed to prove its worth.
With that said, I would say some people would agree that operator overloading and ad-hoc polymorphism are core features of OO languages. In fact, many people learn first about ad-hoc polymorphism through an OO language, such as learning about Java Interfaces or Traits in Ruby.
So in that sense, TypeClasses do relate to OO.
This is where things get muddy. What is OO, what is FP? If you keep yourself stuck in the taxonomy you'll never leave it, because choosing labels is a subjective process. You can put the boundary wherever you want, there is no success criteria.
That said, there are real concentre ideas and implementation strategies. Those are more interesting, but don't get talked about very often.
Some of OOs main features:
- Data encapsulation
- Polymorphism
- Behavior abstraction
And Haskell similarly puts a lot of emphasis on those same features.
So what does one mean if they say OOP is bad? That those features are bad? That would transitively imply that Haskell similarly emphasises bad features?
Now I don't know about everyone, but that's not what I mean if I criticize OOP, I'm not claiming to avoid polymorphism, to get rid of behavior abstraction, and to stop trying to encapsulate data.
So the question is, what's wrong with OOP then? And this is where you'll see that things get nuanced and detailed oriented. And that it's not everything that is wrong, only certain specific things are not ideal and might have better solutions.
And at that point, you can't even speak of OOP generally, you need to speak of specific languages. For example, you could argue that Haskell TypeClasses are a better way to implement ad-hoc polymorphism than Java Interfaces. But someone could say that Self's Traits are just as good if not better than both of those approaches. Etc.
They surely do, OOP like FP, is very fuzzy and since the Simula/Smalltalk days there have been multiple ways to try to represent OOP concepts.
There isn't any OOP language that implements every OOP idea, just like there isn't any FP language that implements every FP idea.
Type classes allow for polymorphism, and combined with modules one can make use of module private implementations for the encapsulation part.
Extensibility can be achieved either by having a type implement multiple type classes, COM v1..vn style, or by delegation.
> They surely do
That misses the point. The original challenge was
> > Can you name a contemporary expert or a platform that started or spent a good chunk of time as FP and ran back towards OOP
and didibus offered
> Hum, I think most did ... Haskell added TypeClasses
But that's simply not true that Haskell added type classes as an attempt to run back to OOP.
For example, OO has polymorphism as a core idea. TypeClasses are an implementation of ad-hoc polymorphism. This concept is a core part of OOP as well as all practical FP languages.
Now if we talk about OOP being bad, and we start talking about ad-hoc polymorphism. That's actually a great concept. It's one of the best concepts to appear in programming languages. So here we are talking about something great which most OOP languages have and make a core part of their offering. Something which Haskell also has and makes a core part of its offering.
Now you can try to move the goalpost, say nah, OOP doesn't mean ad-hoc polymorphism... okay what does it mean then? And maybe you mention class inheritance. Okay, is that it? What about abstraction, data encapsulation, overloading, access scopes, data instantiation, polymorphism, message passing, information hiding, actor-concurency, user defined types, etc. ?
So if you meant to say that inheritance is bad, then say that inheritance is bad. But even here, TypeClasses support inheritance?
> Haskell also supports a notion of class extension. For example, we may wish to define a class Ord which inherits all of the operations in Eq. [...] Note the context in the class declaration. We say that Eq is a superclass of Ord (conversely, Ord is a subclass of Eq), and any type which is an instance of Ord must also be an instance of Eq.
From: https://www.haskell.org/tutorial/classes.html
So what now? Maybe we actually mean that field inheritance is bad? Or what is it? And that's the kind of technicalities we should be discussing, not just say OOP bad, FP good.
It's quite possible for one to be good and the other to be bad (and I would claim that is indeed the case).
That is the thing, there isn't one OOP.
Pharo also offers traits, not sure if Cinacom and Dolphin also do it.
I believe in OO this concept comes from Self from 1987, where they first implemented Traits. In Standard ML TypeClasses were introduced in 1988 I believe. So it actually seems that OO would have had a form of ad-hoc polymorphism before ML. Now I don't want to debate where the ideas came from, I'm trying to say the "camps" are stereotyping which shouldn't be brought up in a technical conversation. Traits and TypeClasses share the same fundamental idea and purpose, and Haskell TypeClasses is its own variant of that idea, which is slightly different to Standard ML TypeClasses as well, and to how other FP languages do ad-hoc polymorphism, and in OO languages there are many variant on that idea as well.
When I enter these debates I assume we're talking about OO in the de facto sense described above. Under that nomenclature I agree that ad hoc polymorphism is a useful language feature but believe that the OO implementation of it is bad.
The "hate" of OO is a misunderstanding, because most people are confused about what everyone and anyone even means when they talk about it, and that's also true of FP by the way.
So when someone criticizes OO for example, but their criticism is directed towards the fact that in Java, I cannot add methods to the existing HashMap class if I wanted too, and that annoys me. So am I criticizing OO? I'll probably label my article "Why OO doesn't work" or something like that. When I should have probably titled it: "Why it is a problem for Java classes to not be extendable from outside their class". Or at the very least: "Why subtyping for extension isn't always the most convenient mechanism for extension."
I see this happening not just on the topic of OO, but everywhere, and I find in a technical setting this is a dangerous thing to do.
I don't really know how to call this effect. I guess stereotyping is a good word for it.
By your logic: 1. Ducks have feathers 2. All birds have feathers 3. Therefore, all birds are ducks
In any case, Haskell didn't get its polymorphism from any OOP language, so the point is moot. ML was first to introduce parametric polymorphism in 1975 — Java/C#/etc with their "generics" came later. Even ad hoc polymorphism with type classes was proposed 7 whole years (1988) before Java was even released (1995). And sure, the paper mentions that OOP has to solve similar issues, but "solving the same problem" ≠ "being the same solution".
I don’t like object oriented programming
It falls apart when your project gets large
There’s too much performance penalty
It requires you to structure your code (?)
And this is in the year 2020.Because that means people who wouldn't have picked the tool themselves are now forced to work with it.
That causes a WHOLE NEW level of complaining!
Another way of putting it is that the most hated tool in a field is almost always the most used one.
It doesn't take any depth of experience to say "oh, it's a tradeoff, there are no good or bad techniques, only tools in your toolbox". It sounds wise. But often it's plain wrong.
The reality is that the difference between general purpose languages aren't that great. Even when highly conservative languages like Java adopt functional programming elements, they don't really add capabilities, they only change convenience. Lambdas are nicer than anonymous classes but they are still equivalent in functionality. The situations in which you absolutely need a purely OOP or FP focused language are pretty rare. People need a language that does a little bit of everything but doesn't go too deep into either end. The problem with feature rich languages is that they are ripe for abuse. When someone discovers a feature for the first time they are going to test it out. People produce a lot of crap in their learning phase that should be thrown out but it ends up in production code bases.
Electricians are in a mature profession with established training standards. Software developers aren't.
> The reality is that the difference between general purpose languages aren't that great. Even when highly conservative languages like Java adopt functional programming elements, they don't really add capabilities, they only change convenience. Lambdas are nicer than anonymous classes but they are still equivalent in functionality.
And yet every substantial Java codebase I've seen uses some kind of XML- or annotation-driven reflection/bytecode-manipulation proxying framework to implement something that a functional programming language would just do. Given how many bugs and complexities these frameworks introduce, no-one would use them if they didn't actually need them. So it seems that in practice the language is lacking something important, even if in theory it's "only convenience".
What if they never give an alternative a chance? Then that scenario could happen quite easily.
Well hate is too strong of a word. I don't hate OOP, but i definitely think it's a mistaken paradigm.
I think there's actually an opposite phenomenon going on side by side with the one you discussed.
Too many senior programmers are really great experts at OOP while never really examining the faults or mastering alternative paradigms. The time invested into OOP leads to a huge bias in favor of it. When OOP is evidently (to me) too be worse than procedural programming and FP people are unwilling to give up years of time invested into OOP. When experts decry OOP of course someone with 10 years of experience in JAVA is going to object. That's the way people work.
The bias comes from both directions. If you think OOP is pretty good and everyone else is biased... ask yourself are you a master at OOP and FP or just OOP? If you're great at both paradigms and the time invested into both paradigms are equal... than you have the ability to make an unbiased choice. Until then most people on this thread are java guys promoting the skill they've been honing for years.
A master carver isn't going to decry his years of training just because a 3D printer can beat him at his game. Humans don't work that way. This is mostly what's going on.
Thank you for demonstrating my point :p
This is exactly what I'm referring too. The use of hyperboles or blanket statement by experts, which are taken at face value by those learning from them. Whereas your real sensibilities are well reasoned, nuanced, and your disliking of OO is in the details. Not everyone can get that from your reaction. So it creates this fashion trend, it's trendy to hate on OO. That's not constructive, because experts also know there's a lot to learn from OO, and not everything about it is bad.
Isn't this statement the same? You don't get into the details of who these experts are, what they like about OOP. You summarize the issue because that's what humans end up doing rather than writing a whole essay about what's so great about OOP. Also you're communicating with someone who you presume already knows the details. You're a parody of your own statements.
You create the opposite fashion trend to hate on people who hate OOP which I see plenty of nowadays. Literally half the posts on this thread follow your ideology:
"I don't fully understand either paradigm so rather then picking the fashion trend I decide to make up a new one and be one of those people waving white flags and calling everything apples and oranges because I can't figure out which is worse or which is better."
Since you're unable to find one paradigm better than the other you think others are just being opinionated on the details, so you declare: It's All the Same! Everything is equal! This is pure fantasy. OOP does not stand on equal ground to FP or even Procedural programming. It's like comparing Hitler to Mother Teresa, not all things in this universe are equal.When you look deeply you will find that there IS absolutely something wrong with OOP. It's not a little detail thing. There is something fundamentally wrong with OOP. Not only that, but you will find that the "experts" in CS have largely discounted OOP.
I can assure you the "experts" largely discount OOP by a huge margin. OOP doesn't even have a formal definition.
By experts I mean academics who are experts in programming language creation using formal and mathematical theory. Someone like Martin Fowler is not an expert because all Martin Fowler does is classify concepts with qualitative descriptions and big words.
Let me clarify. An example of an expert in PL theory is someone who can create a programming language that can prove itself correct. Essentially programming languages that are not only type safe, but logically safe to the maximum degree. Idris is an example of one of these programming languages. Imagine writing in one shot, no incremental testing a 9000 line program with zero bugs. Studies in formal verification enable this with cost... and people who know this stuff are an example of experts in the field.
An example of someone posing to be an expert is someone drawing 3 boxes and using lines to connect it to a single parent and calling that the "Trinarium Pattern" in order to sound like an expert. This is largely what "OOP experts" are including Martin Fowler.
You don't believe me? Why don't you try to find a book on the formal theory of OOP. One that starts off with the axiomatic foundations of OOP. You want to know why "lambda calculus" books exist but there are no "Object Oriented Calculus" books? Because there is no formal theory of OOP period. It literally doesn't exist among "experts."
If anything this post should at least point you or someone in the direction of why OOP is just bad. OOP doesn't have formal foundations... no one has bothered to turn it into a theory or science because it's just that bad.
For a person trying to decide if they should learn OOP or not, I believe the above statement (along with the entire comment) is exactly what parent comment is talking about. A "noob's" reaction to that statement would be "what does that mean?", "why should I care?", "is this important?".
For other "noobs", the reaction would be "HN user leafboi seems an expert so I would stick to what he/she says even if I don't understand what he/she means and I will quote him/her every time an OOP discussion comes up. Fuck OOP".
Meanwhile there are still millions of projects done with OOP, including super-complex AAA games, and more are being done right now...
Is it small wonder that OO languages are more popular when the vast curriculum of the current generation of programmers were raised on python or Java?
A noob might pick what to learn based off of what I'm saying (learn OOP btw, noob, it's bad but it's pervasive) but he's not going to quote something he doesn't understand or agree with.
>Meanwhile there are still millions of projects done with OOP, including super-complex AAA games, and more are being done right now...
OOP is not bad because you can't make things with it. It's bad because OOP abstractions do not add any real benefits while hindering reusability. That's all.
You can still build plenty of things with OOP. It's like javascript. A universally agreed upon bad language with stupid gotchas made in a week but pervasive due to circumstance.
I like encapsulation of mutable state the most.
Almost all software needs to read and write files, as streams of bytes. The state of the open file handle is incredibly complicated, besides the obvious size + offset it includes effective permissions, OS caches, inodes (linux) / NTFS (windows), and progressively more details on each level of abstraction.
It’s not practical to expose all that stuff to a user who only needs to open a file and write a few bytes. OOP allows for nice abstraction over that mutable state. Here’s an example for C++ or C#: https://docs.microsoft.com/en-us/uwp/api/windows.storage.str... Conceptually, procedural languages like C are doing that too for files, they’re just writing object.Verb as object_verb(Handle instance).
You might reply “files are already implemented by OSes, no one is implementing them” and you would be correct. Still, many real-life I/O problems are conceptually (from software design viewpoint) similar to writing bytes into file handles.
In networking-related code it’s not uncommon to layer multiple levels of network protocols on top of each other, OSI-like https://en.wikipedia.org/wiki/OSI_model Hiding implementations of lower levels of the stack from consumers on higher levels simplifies a lot of things everywhere. OOP with interfaces is an idiomatic way to express that thing in programming languages.
The benefits are collaboration (once the interface is designed, two persons/teams can independently work on implementation and consumer), testability (possible to replace the implementation of lower levels with a mock to test just higher levels), runtime dispatch (possible to build the protocol stack dynamically, e.g. either insert or skip the encryption), and modularity (different levels of the stack can be implemented in different DLLs, even by different companies).
The main problem with OOP is the promotion of mute-able state, this is what ties everything together and makes everything less modular. Though semantically equivalent object_verb(object) is actually better than object.verb when you consider mutations and side effects.
Seriously I want you to consider this: Two classes A and B.
class A:
def __init__(self, x):
self.x = x
heavy_computation()
def addOne(self):
self.x += 1
class B:
def __init__(self, x):
self.x = x
def addTwo(self):
self.x += 2
Let's say I want to write an addOne method for B. How do I reuse the code for addOne in A inside of B? 1. Inheritance. Make a base class have A and B inherit from it.
2. Duplicate code by writing a duplicate method.
3. Dependency injection, modify B so that it takes A and Does some crazy stuff:
class B:
def __init__(self, x, a):
self.a = a(x)
self.x = x
def addOne(self):
self.a.addOne()
self.x = self.a.x
def addTwo(self):
self.x += 2
self.a.x = self.x
All three options are bad for various reasons. There is no option where I can just add a method to B without modifying any other part of the program or duplicating code.AddOne is not reuse-able because it's tied to mutable state and the constructor and all the methods that share that state. This happens regardless of whether or not it's A.addOne() or addOne(A).
The only universe where addOne can be reused is if it started off as a stateless function (addOneFunc)
def addOneFunc(x):
return x + 1
class B:
...
def addOne(self):
self.x = addOneFunc(self.x)
By making addOne a functional concept I am able to impliment B.addOne without modifying anything else and reusing code to maximum effect. You cannot do this easily with OOP and this is why OOP is bad.If I want to deal with mutable state, it's actually not technically FP anymore. Inevitably I will have to and that's why many FP paradigms segregate IO with FRP patterns or the IO monad. Even still straight up procedural programming with mutability is still better than OOP because addOneFunc:
def addOneFunc(int& x):
x += 1
is more reusable than addOne: class Jungle:
def __init__(self, x, gorilla, banana):
self.x = x
heavy_computation()
self.jungle = create_jungle(create_gorilla(banana))
def addOne(self):
self.x += 1
“… Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. “ —Joe ArmstrongMany people will say don't design your programs this way. Well nobody has the foresight to know exactly how their primitives will compose for the design they have now and in the future. With OOP you will inevitably design your program into a wall because when it comes time to reconfigure the bricks in the wall you realize that OOP has super glued everything together. What happens IRL is that people eventually hit more complex versions of the example problem I gave you above.
When the mutable state is essential (like in my example for a file stream object) that’s not a problem at all, quite the opposite, it allows to expose idiomatic API from a library/module, while hiding implementation details from consumers.
> Though semantically equivalent object_verb(object) is actually better than object.verb
All modern IDEs have auto-complete, you type x. in any statically types OO language and you’ll get a list of all public methods of the class. object_verb(object) can’t do that kind of contextual stuff.
> All three options are bad for various reasons
They’re also all good for other reasons. This applies to all options whatsoever, not just to these 3. Software engineering is all about the tradeoffs, which tradeoff is the best depends on requirements, language, platform, team, budget, users, and many other factors.
1 is good when the majority of the implementation is shared, and A/B only need to override a couple of methods. Many OO languages have private/protected/public visibility to be able to selectively hide implementation details even from derived classes. An example is a video encoder or decoder: it has 90% of code shared across all codecs, and only the remaining 10% depends on whether the codec is h264 or h265.
2 is good when the code is simple like your example. Also when you expect you might want to change the method in one of the types but not the other despite they’re equivalent right now. Also that version is the most readable of them.
3 can be good when you expect you might want to change the implementation of the object being injected while keeping the interface of that object, albeit I don’t normally write stuff like that unless I have very good reasons.
Your method is bad if the code is performance critical, your call stack is twice deeper than 1 or 2. Even ignoring performance, deep stacks complicate debugging, all debuggers show call stacks, they may also be visible in other places like crash dumps, exception traces, or logs.
> If I want to deal with mutable state, it's actually not technically FP anymore
I don’t particularly want to, but for 99% of real-world software dealing with mutable state is essential part of the problem.
A lot of software is I/O bound and/or the I/O is the main source of their complexity. Database engines spend majority of time doing disk and network I/O. For servers and web apps that’s network I/O. Videogames mutate external state in VRAM at a rate of gigabytes/second. Every device connected to the Internets runs many thousands of lines of code layering protocols on top of each other, a session on every layer is a large piece of mutable state (buffering everywhere, decompressor state for GZIP, decryption stream state for TLS, and so on).
GUI apps have a huge chunk of mutable state. For an editor like Word it’s obvious why, user can edit anything however they please. But that’s true even for readonly or mostly read-only GUI, e.g. most web pages are read only yet the browsers implement mutable DOM trees, the second letter of the acronym is for “Object”.
Yes, When mutable state is essential pure FP cannot help you. However much of FP styled languages / frameworks are designed to segregate mutable state away from your awareness as much as possible. See React or Haskell.
Even so, like I illustrated in the Jungle banana gorilla example. When you have mutable state, it is better to deal with it without using OOP. Take a look at that example again, see how the procedural function is better than the OOP method.
>Your method is bad if the code is performance critical, your call stack is twice deeper than 1 or 2. Even ignoring performance, deep stacks complicate debugging, all debuggers show call stacks, they may also be visible in other places like crash dumps, exception traces, or logs
We can talk about the qualitative benefits of each but I'm sure you'll agree from a language/API perspective... My method is the best method in terms of reusability. If the focus of your code is reusability and modularity FP is the best way to go. However you are right that there is inevitably a performance cost. Computers are essentially machines that rely on mutability and have memory boundaries. Any functional abstraction implementation on top of such a machine will not be a zero cost abstraction. We're in agreement here. However you will note that for most modern programming the performance cost is no longer a factor that matters. Either computers are way too fast and make the performance cost negligible or there are bottlenecks in other parts of the system that make the performance gain negligible.
Games and Databases are two examples where FP may not be the most appropriate paradigm due to the performance critical nature of the areas.
>I don’t particularly want to, but for 99% of real-world software dealing with mutable state is essential part of the problem.
Yeah A lot of FP languages have abstractions that help you get around this. See Haskell or React, look up FRP and the IO Monad. Not trivial research subjects.
Look think about it this way. OOP promotes shared state and mutable state in places where it isn't necessary. FP forces you to use a minimum amount of shared as possible and segregates that away from your program. There are essentially two types of functions:
functionThatGetsDataFromIOOrChangesState()
functionThatTakesInputANdHasAnOutput()
FP has abstractions that highly segregates these two types of functions. OOP has facilities that integrates all these functions together into arbitrary groups called objects. The purpose of a class is to hold mutable state and integrate side effects with logic. If your class does not hold mutable state then essentially it's just a class repurposed as a namespace with syntactic sugar around the object_verb(object) semantics.If you are writing a low level program that deals with a lot of mutable state or IO, FP can still operate in this arena via the abstractions but if performance is super critical then probably don't use FP for this stuff. Still, better than OOP for mutable stuff is just a straight up procedural paradigm as I showed you in the banana jungle gorilla example.
IO and mutable state needs to be separate from Logic in order to promote reusability. This is the key area where OOP fails. Procedural programming can achieve this separation while Functional Programming Forces you to achieve this separation.
FP has its downsides and upsides, it is however the most reusable and modular paradigm that currently exists. If modularity and elegance is your objective nothing I know of beats it.
As for OOP, when you bring in procedural programming into the picture, OOP no longer has any upsides. Again, see the banana example.
What about React? They say right on their front page: “Build encapsulated components that manage their own state, then compose them to make complex UIs.” That’s a textbook definition of OOP.
> it is better to deal with it without using OOP
That’s a famous example but why do you think it’s always better? Sometimes, like when dealing with graphs, holding the jungle with the banana is exactly what you want.
> If the focus of your code is reusability and modularity
My primary focus is meeting requirements and budget. All technical decisions about reusability, modularity, performance, and the rest of them, are driven by these two.
Some projects don’t need reusability at all, because that’s research, or a proof of concept, or a video game that won’t be maintained once launched.
In other cases, being too aggressive about eliminating duplicate code can harm long-term maintenance. More likely to happen in higher-level parts. You introduce an abstraction, then you need to handle just one special case where the code being reused needs to behave differently and introduce a parameter. Repeat a few times and you now have tons of parameters, flags, and other crap no one understands, not even you. This happens regardless on OOP/FP/procedural, the crap is different in these cases but its effect on maintenance cost is equally bad. Wouldn’t happen if you would be more conservative about introducing abstractions, and instead copy-pasted a few lines of code here and there.
> for most modern programming the performance cost is no longer a factor that matters
Almost everything has a performance budget. For a web app difference between 1ns and 1ms doesn’t matter at all, but difference between 100ms and 1 seconds still matters, users will complain, search engines will downrank. Desktop apps ideally need to respond within 17ms because mainstream displays render at 60Hz. Performance cost of immutability is huge, can be 1-2 orders of magnitude, for 2 reasons. (1) memory allocations are relatively expensive (2) CPUs have cache hierarchy, main memory is like 20-40 times slower than L1D cache, each time you allocating a new immutable object it’s pretty much guaranteed to be out of caches.
Still, there’re many cases when immutability is fine. C# immutable structs are awesome, unlike classes they’re free in terms of memory allocations, and they are usually on stack which guarantees they’re well cached. The upcoming C# 9.0 has a language support for immutable classes, pretty sure I’ll use it a lot once released and debugged.
> Games and Databases are two examples where FP may not be the most appropriate paradigm due to the performance critical nature of the areas.
Also CAD/CAM/CAE, realtime multimedia, some projects in embedded esp. real-time stuff. By the way, these areas are what I mostly do for a living.
> If your class does not hold mutable state then essentially it's just a class repurposed as a namespace with syntactic sugar
I like that sugar, and using that approach quite a lot in my code. For example, private methods and fields are invisible from outside, makes life easier because they don’t show in intellisense. I disagree that OOP promotes mutable state for cases when it’s not needed, it all depend on the language. JavaScript or Python don’t support immutable properties of their objects, but C# and (to lesser extent, but still) C++ do.
However, the real value of OOP is when you have to deal with complex mutable state. Every single time mutable state is essential to the problem (rich GUI is just one area where it’s the case, there’re others), OOP has no good alternatives.
I made a mistake here. Not react. Redux. Basically the DOM update loop that's popularly associated with languages like elm and frameworks like react. That world changes so fast I'm not even sure if Redux is relevant anymore.
>My primary focus is meeting requirements and budget. All technical decisions about reusability, modularity, performance, and the rest of them, are driven by these two.
Then do what you need just know that OOP doesn't contribute to reusability. I actually don't see any benefit to is over just procedural programming.
>Almost everything has a performance budget. For a web app difference between 1ns and 1ms doesn’t matter at all, but difference between 100ms and 1 seconds still matters, users will complain, search engines will downrank. Desktop apps ideally need to respond within 17ms because mainstream displays render at 60Hz. Performance cost of immutability is huge, can be 1-2 orders of magnitude, for 2 reasons. (1) memory allocations are relatively expensive (2) CPUs have cache hierarchy, main memory is like 20-40 times slower than L1D cache, each time you allocating a new immutable object it’s pretty much guaranteed to be out of caches.
Nah most CPU stuff is so fast none of this matters anymore. For web development, (I'm a web developer) They use languages like python with garbage collectors to handle stuff. The reason is because the bottlneck lies in IO. Python is slow but the database is slower and that's where they use a procedural language like C. For browser stuff they use javascript now and the DOM and the redux pattern so pure performance is no longer as critical because stuff is so fast anyway. I mean really when the browser is loading you're waiting on IO, not on rendering or javascript.
>Also CAD/CAM/CAE, realtime multimedia, some projects in embedded esp. real-time stuff. By the way, these areas are what I mostly do for a living.
That's similar to gaming right? Graphics. So essentially yes you do work in an area where FP is probably not the best choice. However the majority of programming nowadays is Web sites. Even so JS and functional programming is powerful enough to drive 3D graphics on web browsers.
>However, the real value of OOP is when you have to deal with complex mutable state. Every single time mutable state is essential to the problem (rich GUI is just one area where it’s the case, there’re others), OOP has no good alternatives.
What's the difference between a function that modifies state and a method that modifies it's own internal state? Almost none except the method is tied to context. The method cannot be used outside of that context.
You have to instantiate state and irrelevant context to use a method and that is the problem. The alternative to OOP is to just use procedural programming because literally there is only one single difference that separates the two paradigms:
Methods are tied to context, functions are not.
In knowing this you will know that basically with OOP you're tying your hands together for no good reason. You can write those exact same methods as separate procedural functions to get the same functionality without the downsides.
> I disagree that OOP promotes mutable state for cases when it’s not needed, it all depend on the language. JavaScript or Python don’t support immutable properties of their objects, but C# and (to lesser extent, but still) C++ do.
It promotes it by virtue of being the only feature that separates a class from a namespace. If you're not using a mutable variable in your class you're not doing OOP because what you're doing is isomorphic to writing a namespace with functions. (You can have private functions in namespaces for certain languages to btw.)
As I side note to easily about the FRP pattern you should just checkout ELM. https://elm-lang.org/examples/first-person. It's a batteries included functional programming framework that you can program right in the browser. In the example I gave you the first person 3D program is written by a user without the user ever writing a procedure or a method that modifies state. All the functions minus the shader stuff are strictly pure and therefore reusable.
Have very little idea, I don’t do web development.
> That's similar to gaming right?
There’re differences e.g. games render at 60Hz, games don’t run CPU-bound jobs which load all cores of all CPUs for minutes to hours. But overall, indeed it’s much more similar to games than to web.
> Graphics
I would estimate graphics is 25% of code complexity in games (however most modern games are using off the shelf engines who implement major parts of that), and 10% of complexity of CAD/CAM.
> the majority of programming nowadays is Web sites
I think the majority of programmers nowadays, and have been for decades, working on internal line of business applications. No reason to generalize their experience over the rest of the software development.
> Even so JS and functional programming is powerful enough to drive 3D graphics on web browsers.
JS is merely passing data to WebGL APIs. These APIs are implemented in web browsers, and underneath browsers in GPU drivers. Both modern browsers and GPU drivers have millions of lines of code written in C or C++, and JS is not performant enough to replace these, not even close.
> What's the difference between a function that modifies state and a method that modifies it's own internal state?
Visibility of that state. Usability of the API. Composability of the overall thing.
> The method cannot be used outside of that context.
Yes, and that’s usually a good thing. Here’s a method: https://docs.microsoft.com/en-us/uwp/api/windows.storage.str... You can’t write bytes to a stream without the stream. From API design perspective that’s a feature, not a bug.
> What's the difference between a function that modifies state and a method that modifies it's own internal state?
For some cases, runtime polymorphism helps a lot. In that case, neither you nor compiler knows what’s in the state or how’s that implemented, it only determined in runtime. Take a look at this toy API in C#:
https://gist.github.com/Const-me/0745e09695e8e8615c14b18fc3f...
The internal state of the object returned from setupConnection method can be just the TCP socket, or it can be a socket + AES decryption + a decompressor.
The decrypt, decompressGzip and decompressLz4 functions are usable for any streams not just TCP sockets, you can pass them a file or any other stream and they’ll work just fine, despite their internal state changes to something completely different.
Then I would say you are a bit behind the curve when it comes to what the majority of people are doing in UI development. The majority of UI development happens on the web. Current practices involve using a functional paradigm to deal with mutable state. The pattern is called Functional Reactive Programming. FRP for short.
>I think the majority of programmers nowadays, and have been for decades, working on internal line of business applications. No reason to generalize their experience over the rest of the software development.
I'm generalizing over a vast majority. The vast majority of SW development is web stuff and basically in web development performance is not the priority. Useability is. Basically people choose languages and frameworks like ruby, php and python over C++ for ease of use. Software development bootcamps emphasize javascript not C++.
These languages are so slow that it doesn't matter if you follow the functional style or not.
>JS is merely passing data to WebGL APIs. These APIs are implemented in web browsers, and underneath browsers in GPU drivers. Both modern browsers and GPU drivers have millions of lines of code written in C or C++, and JS is not performant enough to replace these, not even close.
Yeah I know. Don't write your OS or OS drivers in javascript. In general the platform is implemented by one guy and everyone else operates in his universe. Most web developers and even game developers won't get into the internals of OpenGL.
>Yes, and that’s usually a good thing. Here’s a method: https://docs.microsoft.com/en-us/uwp/api/windows.storage.str.... You can’t write bytes to a stream without the stream. From API design perspective that’s a feature, not a bug.
You missed the point. Object.write is worse than write(stream) because Object can hold more things than just a stream. If you implemented Object.write instead of write(stream) then your write method is forever tied to irrelevant context. Again see the banana gorilla jungle example.... literally the addOne method cannot be reused without creating a banana, a gorilla, and a jungle.
If you want to restrict your function to operate on certain data types you do this by restricting the input parameters. That's it. There is no need to attach it to irrelevant context when it doesn't even really touch other parts of the state.
>Visibility of that state. Usability of the API. Composability of the overall thing.
In FP you hide private state in functions. It is hidden anyway. "C" below.
A -> function(A){return B(A, C)} -> B (C is hidden state)
If you think about your programs this way where hidden state is ephemeral you never will have objects that are in a state of half correctness. The object only has two states: Existence or non-existence, never a state where only half of it's properties are initialized. The hidden state is forever stored within the function expression, ephemeral and inaccessible as it should be.This is 100x better than the explicit use of "private." Hidden state in this case is truly hidden by structure rather than by syntactic tags.
Useability is equal. As you yourself as said object.verb() is semantically equivalent to verb(object).
Objects are not more composable than functions. The only way to compose objects are through inheritance, (which is bad) and object composition which requires the parent object to be aware of the child Object.
Example:
class B():
....
class A()
def __init__(self, B)
self.b = B
A is aware of B, therefore A is not modular. You have to customize A to be aware of B to compose it with B.
universal function composition:
def compose(f: A->B, g: B -> C): A -> C {
return function(x: A){return g(f(x)); }
}
def addOne(x: int){
return x + 1;
}
def timesTwo(x: int){
return x * 2;
}
addOneThenTimesTwo = compose(addOne, timesTwo)
addOne is not aware of timesTwo, timesTwo is not aware of addOne, compose is universal meaning it works on any function of type A -> B.
In terms of composition. Combining functions to form other functions hands down beats combining objects to form other objects. Literally no contest. It's one of the defining features of FP. All your FP primitives are literally tiny modular bricks that your compose into data pipelines that pump data from Input to Output. It is at the two ends of this pipeline where the functional style breaks down and you have to either start breaking rules or using complex abstractions to deal with IO and mutable state.>The internal state of the object returned from setupConnection method can be just the TCP socket, or it can be a socket + AES decryption + a decompressor.
These are all static and stateless IO functions. You're not really doing OOP without an internal mutable var. You could literally rename that class to namespace. (I'm not too familiar with C# but I'm assuming the use of the word static means that the function is not tied to instantiated state meaning all state arrives in as a parameter negating the need for using the word "class" over "namespace")
Worked with that in .NET a bit, specifically this one: https://github.com/reactiveui/ReactiveUI Not a big fan. For example, when I wanted to fix a bug, put a breakpoint to the code that failed, and reproduced, there was no way to tell who or why sent that event, the original context was already lost. For this reason I prefer simpler ways with interfaces or lambdas. Not sure if that’s a problem of the approach or the implementation I used. But generally speaking marshalling contexts is complicated and is a common source of bugs, YMMV.
> in web development performance is not the priority. Useability is.
I can see how it can be the case on servers (one reason is economy, for many companies it makes more sense to rent more EC2 instances instead of writing better software), but I’m not sure same applies to client. In the browser, you have the same 16.6ms performance budget for optimal usability. The fact that JavaScript is generally slower than compiled languages makes things worse.
> literally the addOne method cannot be reused without creating a banana, a gorilla, and a jungle.
Every design pattern can be abused, and none of them is a silver bullet. If you want to reuse that code, make it a global function, all OO languages have them or equivalents (static classes in C#). But other times, the feature if very useful. See the example linked in my previous comment (I hope C# is readable enough even for non-users), the iReadStream object returned from setupConnection can potentially hold quite a lot of referenced things, GZIP compressor, AES implementation, yet for the user of that object it’s just a trivially simple interface with a single read() method.
> If you want to restrict your function to operate on certain data types you do this by restricting the input parameters.
Due to polymorphism, OOP allows to write functions operating on data types unknown to either compiler or the programmer writing that code, as long as the types implement the expected functionality.
Microsoft replaces both data types and code of their IOutputStream.WriteAsync method with windows updates. Their changes don’t break my code, they don’t even require me to recompile it.
For that particular OS kernel API, you might say we aren’t re-implementing it on our side, and you would be correct. Still, that level of flexibility is very useful for making software. At least once the software complexity grows past some threshold: for simpler stuff it often does more harm than good.
> This is 100x better than the explicit use of "private."
These things are orthogonal. No one forces you to return half-initialized objects, you can throw an exception instead.
> Combining functions to form other functions hands down beats combining objects to form other objects.
Not every object can be expressed as a function. That C# example I linked above can, because iReadStream interface only has a single method, but if we want to do the same for writing, we now need 2 methods, write() and flush().
> You're not really doing OOP without an internal mutable var.
1. Why not? We have objects, they have methods which mutate their internal state. Looks OOP enough in my book?
2. If I would have actually implemented these objects instead of writing // TODO comments, these implementations all would have a fair bit of internal mutable vars, due to buffering, AES blocks, and other shenanigans.
The tooling is irrelevant to the paradigm itself as tooling can improve over time. Either way it's the dominant pattern right now for UI. I'm not an expert in it but I do know that the tooling for react/redux right now is quite good.
>I can see how it can be the case on servers (one reason is economy, for many companies it makes more sense to rent more EC2 instances instead of writing better software), but I’m not sure same applies to client. In the browser, you have the same 16.6ms performance budget for optimal usability. The fact that JavaScript is generally slower than compiled languages makes things worse.
In servers it's not a priority because the database is the bottleneck. Servers need to just be faster than the database and that can easily be achieved with something slow like ruby or horizontal scalability. The pattern in web development on the backend is highly similar to functional programming. Web servers are mostly stateless and state is stored in a database, so you can basically increase the amount of servers that access the database for basically an unlimited amount of capacity for parallelism. Of course all of this is bottlenecked by the speed of your database.
For the front end it's actually less critical, believe it or not. Even the shittiest phone is already plenty powerful. Most of the slowness in this area is still in IO or data transfer. All those loading screens you're waiting on is mostly data transfer and database bottlenecks. The reason why UIs are so slow nowadays is because literally half of most UIs live on the cloud and are downloaded by your browser/mobile device. We are well past the time when UIs take 16.6ms to load. 1s is actually quite good nowadays.
>Every design pattern can be abused, and none of them is a silver bullet
Except I didn't abuse a design pattern. I used an incredibly general example. Basically a single method in a class will likely not be utilizing all members in that class yet at the same time you cannot use a method without instantiating ALL members. it's a pointless restriction that is solved by changing the method into a function that operates only on its input parameters.
When something is wrong with the most trivial of all examples... then something is wrong with the entire paradigm.
>If you want to reuse that code, make it a global function, all OO languages have them or equivalents (static classes in C#).
Which is another of saying "use functions in namespaces instead of objects." I recommend that if you're doing C# or JAVA you should write all your functions this way. There is no reason why you shouldn't have this level of re-usability on ALL your code. There's no point in making things methods tied to random state.
>See the example linked in my previous comment (I hope C# is readable enough even for non-users), the iReadStream object returned from setupConnection can potentially hold quite a lot of referenced things, GZIP compressor, AES implementation, yet for the user of that object it’s just a trivially simple interface with a single read() method.
I looked at it, the use of static keyword makes it basically plain old functions rather then methods. I wouldn't call it OOP. But whatever it's just words. Basically I'm saying: "shared mutable state in classes offers no benefits and makes your program less modular" You can still call it OOP if you want but if you agree with my statement then overall we're in agreement.
>Due to polymorphism, OOP allows to write functions operating on data types unknown to either compiler or the programmer writing that code, as long as the types implement the expected functionality.
This is just a type class or an interface or whatever you want to call it. It exists in other languages as differently named concepts. I would say it's not an OOP thing. An untyped language like python basically every single variable type in that language is Polymorphic to each other.
>Microsoft replaces both data types and code of their IOutputStream.WriteAsync method with windows updates. Their changes don’t break my code, they don’t even require me to recompile it.
That's because you already bent your code to fit their interface. They maintain that interface and you don't have to change anything. This is universal to all types of programming paradigms FP included.
>These things are orthogonal. No one forces you to return half-initialized objects, you can throw an exception instead.
In an FP language you are forced to never even be allowed to have half-initialized objects exist. The concept doesn't exist in FP. This eliminates a whole class of potential errors from your program.
In procedural languages a common pattern I see is this:
var x = A();
x.a = 1
x.b = 2
....
Basically if the procedures are written out like that it leaves room for a potential error/mistake/bug to occur. What if you miss instantiating something?In a functional language it must be done only like this:
var x = A{1, 2, ...}
And most FP languages do not allow nulls or future modification. Meaning you have to figure out the true value of every variable and shove it into the object during instantiation. These are essentially procedural or temporal errors that functional programming gets rid of by eliminating the concept of time. It asks you to define the correct piece of data rather than treat it like a container that you fill with data over time.The language Elm that I recommended earlier is an FP language that takes concepts to the extreme. In ELM you can absolutely never have a runtime error (other then blowing up memory). It is impossible because the language doesn't allow you to do stupid things that would otherwise trigger runtime errors in other languages like C# or C++. It eliminates a whole class of errors you find in C, C++ or C#. The only thing you have to deal with is logic errors.
>1. Why not? We have objects, they have methods which mutate their internal state. Looks OOP enough in my book?
You referring to your example? I dunno it looks to me like they're static meaning they don't mutate internal state. Do those functions in your example modify variables that aren't part of the parameters? I think I'm being liberal with my use of "mutate internal state." In this case I mean a member variable of the class. You labeled it static and therefore the function is basically just a procedural function. I wouldn't call it OOP anymore then I would call a function in C OOP.
>2. If I would have actually implemented these objects instead of writing // TODO comments, these implementations all would have a fair bit of internal mutable vars, due to buffering, AES blocks, and other shenanigans.
Well they aren't mutating global vars are they? If it's just mutating scoped variables within the function then it's still not OOP in my book. It has to modify a variable outside of it's scope that's part of the instantiated class. I wouldn't call this function FP either. It's definitely procedural programming similar to C or golang.
To make it clear I'm saying Procedural programming > OOP and FP > OOP and it's still up in the air for procedural programming vs. FP.
Also the in the banana gorrila example I compared a class to a function.
def addOneFunc(x):
x += 1
Just so you know the above example is not an example of FP. That is a procedural function that mutates state. I was using it to show you how even Procedural programming is better than OOP.I've heard that OOP in game engines is in decline with so-called "Data Oriented Programming" taking over, that Unity's inheritance class library was a disaster, etc.
So, it's not FP per se, but a focus on data over "objects" is a big part of FP being about "values" instead of opaque state.
Yes precisely. I'm not trying to blame experts or people learning from them, I'm just describing the phenomenon, too which I am not exempt from. I'm just as much a part of it as everyone else, and I've been in both sides, the expert side, and the learner side.
I try to be more aware of it now, and tone down my statements or be more precise on the extent of my criticism, when in the expert side. And when in the learner side, I try to be more skeptical of blanket statement, and not to take everything at face value, to look for the details and read up on the nuances so I don't distort and bias my understanding.
Understanding that phenomenon explains why even if it appears as if everyone hates OOP, it is still widely spread and in use, basically because it's much more complicated then that.
I won't address the rest of your comment, because I'm actually a functional programmer in my day job, even though I've done ton of OOP prior. I prefer FP, but in reality, I prefer the convergence of good ideas which many modern languages are bringing, and for whom the ideas come from OO and FP alike.
Edit: Okay, I will address one thing. An expert in PL theory is not an expert at writing programs or at engineering business solutions that leverage the known science of computers.
Who cares. I don't think n00bs are taking what you say and running with it without understanding it. It's not like they're going to quote something they don't understand. The hatred for OOP is from a genuine belief that they understand the concepts (and so is the love.) If anything what you say could only change the direction of what they choose to learn. I doubt they'll quote or promote anything they don't fully understand.
>An expert in PL theory is not an expert at writing programs or at engineering business solutions that leverage the known science of computers.
Nobody is an expert at this because there's no formal theory behind "business solutions" It's all people making shit up and walking in circles. Is your arbitrary solution the best? Is someone else's better? Maybe your solution is just mediocre how do we rate any of this what exactly is a "good business solution?" This whole field is largely an art.
So when a field is an art you get people liking terrible drawings done by a three year old, take a look:
https://i.pinimg.com/236x/5f/c9/4f/5fc94f54df98d50bfae9b1807...
Wait I lied it's Pablo Picasso. This work is the work of a genius according to most people. But you can definitely see why from other angles it's considered pure crap. Is this guy an expert or is he on drugs? Maybe both, nobody knows... same with business solution architects.
Because nobody can formally define "good business solutions" most of these "business solutions" including OOP is just like that piece of art above. It's all opinion and people making shit up.
So just discount it all. Modern art to me is a form of fraud. And so is a business solution architect or an OOP architect. Follow the science, follow what's definitive.
https://pages.cpsc.ucalgary.ca/~robin/FMCS/FMCS2014/An%20int...
https://en.m.wikipedia.org/wiki/Luca_Cardelli
There's also definitely a lot of published papers about typing OO, like https://ieeexplore.ieee.org/document/316056 , https://ieeexplore.ieee.org/document/287603 , https://drops.dagstuhl.de/opus/volltexte/2016/6103/
I'm also not really sure what you're trying to point out. When you talk about a calculi for FP, you're talking about what? Lambda Calculus?
If so, the base mathematical model of OOP is the Turing machine. That's the model of computation involved. It's been extensively researched and is absolutely well accepted and recognised by everyone everywhere.
Similarly to how there was no mathematical model of Haskell or Idris, etc. There wasn't any for OOP languages either like Java, C++, etc. Any attempt at a mathematical framework over those languages came after, and none of them have a strong one that I know off. Some of them, maybe most especially Idris, did get a lot of inspiration from known typing mathematical models, but still the language itself does not have a full on calculus of itself (correct me if I'm wrong)
If you're talking about type theory and type systems as related to provability of computation, that's like a whole other conversation. But even then, there's ton of literature that research type systems that can prove OOP, often focused on subtyping since that's the main typing discipline of most OO languages with static type systems.
See Luca Cardelli, A Theory of Objects. I might agree with you that the formal foundations of OOP are far from intuitive or foolproof (which is why everyone cautions you against using, e.g. implementation-inheritance unless you know exactly what you're doing!) but they do exist.
This book has like three reviews on amazon and in general a formal theory doesn't exist. You won't find a formal theory about OOP calculi on wikipedia.
There are thousands of people in academia and of course at least one of them will attempt to formalize OOP. But largely said and done, overall there is no formal theory that is well accepted. Just random arbitrary attempts at formalization.
There are other programming paradigms and there are of course multi-paradigm languages. As a for instance I do most of my work in Prolog these days (because PhD) which is a logic programing language, so neither OOP nor FP but, er, well, LP. There's many logic programming languages that mix multiple paradigms like Logtalk (LP and OOP), Visual Prolog (LP and OOP) Mercury (LP, OOP and FP), OZ (LP, OOP, FP, constraint, concurrent) etc etc.
I mean, I understand that OOP and FP may be the only relevant paradigms in a particular context- but, in that case, what is the context that is assumed in this thread? Web development? App development? Games development? AI? Data science? Embedded systems? Compiler writing?
EDIT: I guess this "OOP or FP" thing reminds me of myself at my first undergrad CS year. I knew a bit of JS/HTML/CSS, a bit of Java and C#, a bit of SQL and had heard of C and C++ so I was trying to place them into categories, like "C#, Java and C++ are OOP, C is procedural, that's it, there's no other paradigms". Then, in the second year I met Prolog and it kind of blew my mind. It's good to have one's mind blown like that, though it stings at first when one realises how little one knows and how many assumptions one built on such little knowledge. One feels a little dumb. But one quickly gets over it because there is suddendly a world of interesting new things to learn.
And the technical definition of OOP isn't formalized either so in some cases you can do FP and OOP at the same time as well. However, mutation is very very often used with OOP so if mutation encompasses the definition of OOP then it remains separate from FP.
Either way as you go up the ladders of abstraction things become much hard to control.
I think LP is the theoretical next step.. the next level of abstraction. It's like 3D printers. We want to build a 3D printer that can print out a working motorcycle but without fine grained control over the process it's hard and still WIP.
Above LP would be like alexa or google home. Imagine trying to build something by describing it in the english language.
Either way the reason why LP is not pervasive is because programs are generally fine tuned precision parts. It's the same reason why we still build houses with construction workers not 3D printers. The closest you get to LP in the real world is SQL. The problems I describe are evident for experts in SQL. SQL experts are largely people who "hack" at the syntax trying to bend it in arbitrary and unexpected ways to get a performance boost.
stuff like
SELECT * FROM TABLE_WITH_TWO_ITEMS;
vs. SELECT item1, time2 FROM TABLE_WITH_TWO_ITEMS;
are just arbitrary hacks that have no surface level meaning. (Supposedly the second expression is not as performant in case you're not that familiar with SQL hacks). I guess a good way to describe this are the words "leaky abstraction."If we move too far in the declarative direction we tend to hit more and more leaky abstraction problems. Essentially the programmer needs to understand the surface API as well as the internals. Additionally Lower level optimizations begin to be expressed as high level isomorphic expressions with no explicit reasoning (IE sql syntax does not explicitly tell you why item1 and item2 are faster then *).
That’s a bit abstract. Basically a lot of good cake from OO. It’s not all good, sure, but polymorphism is a pretty darn good idea. Substitutability is a good idea. OO won’t solve all of your problems for you, but it isn’t inherently creating your problems either.
Most importantly, not everyone hates it. There are definitely warts involved, and IMO the 2 biggest warts are now largely understood:
1. People understand the pitfalls of inheritance now, so you'll see "composition over inheritance" MUCH more than you'll see inheritance as the recommended way to do things.
2. I don't know how widespread this feeling is, but as someone who moved from Java to Node/Typescript a couple years ago, I feel like I can confidently say structural typing, in practice, is MUCH easier, better and fun to work with than nominal typing. I can think of times I literally spent days trying to work through some refactoring in Java that was a nightmare because of the constraints of nominal typing, where the structural typing approach of TypeScript makes it trivial.
But if I don't have inheritance, then what? Do I have different instances of Foo that have different implementations of f(), but they're still all Foo (rather than something derived from Foo)?
Yes, these are called "Trait objects" in Rust.
Inheritance: T IS-A Foo
Composition: T HAS-A Foo
Neither: T IS-DESCRIBED-BY Foo
"Component Software: Beyond Object-Oriented Programming"
https://www.amazon.com/Component-Software-Beyond-Object-Orie...
https://www.amazon.com/Component-Software-Object-Oriented-Pr...
If both objects `Foo` and `Bar` have a method called `baz`, calling `object.baz` will dispatch to the objects `baz` function, no inheritance needed.
In that way, there are 3 ideas when you have inheritance. The first is reuse of code. The second is a relationship between 2 classes/types such that you get the Liskov Substitution principle. The third is dynamic dispatch based on object (or type/class in most cases).
Almost all FP languages have the third (type classes, traits, generic interfaces, the list goes on) and the first (default implementations of traits/typeclasses) but none have the second.
In Python defining function f is enough for a class to be a Foo (and Foo could not exist at all in code, replaced by the informal concept that a type with method f is a Foo)
In languages like Java, there are interfaces, which are not classes at all and describe with tedious compiler-friendly explicitness what a class needs to qualify as a Foo.
On a different variability axis, sometimes you need to declare interface implementations explicitly ("class Bar implements Foo" in Java) and sometimes not (Python duck typing, C++ concepts)
And then you have Bar and Baz that implement trait Foo.
Example:
pub trait Foo {
//
fn f(&self) -> String;
// Default implementation, can be overriden
fn banana(&Self) -> String {
String::from("BananaNjam!")
}
}
pub struct Baz {
pub BazThingy: String,
pub Whatever: String,
}
impl Foo for Baz {
fn f(&self) -> String {
format!("{} {}", self.BazThing, self.Whatever)
}
}
pub struct Bar {
pub Bar: String,
pub Tralala: String,
}
impl Foo for Bar {
fn f(&self) -> String {
format!("{} {}", self.Bar, self.Tralala)
}
}
// Now function that uses Foo, could be any object that implements foo
pub fn FooFighter( item: &impl Foo) {
println!("FooFighters rocks because {}", item.f() );
}
The neat thing is that Bar and Baz does not have to share all of the traits. SO you can have default trait lets sey Debug, that prints object, on most objects, but Foo only on a few, if that makes senseAgreed that structural typing is nice, but it's not restricted in any way to OOP.
Totally agree, but what I'm arguing is that a lot of the pain points people associate with OOP are actually more about the pain points of a nominally-typed system, and when you use OOP with a structurally-type language a lot of those pain points go away.
Object mutability is an attractive nuisance.
Which is a shame because I like bits of OOP.
i see this kind of code alot:
person {
age: UInt
birthday: Date
}
whereas if you have any sense of when "now" is its much better to just calculate the age based on current date person {
age: UInt {
birthday.YearsUntilNow() //slightly trivialized ^^
}
private(set) birthday: Date
}I agree though, if your classes are so big it's difficult to keep track of which methods are updating private member variables then that's a good sign your class is too big.
Structural typing is crazy flexible, but you pay for it with worse type error reporting (forgetting something in an interface is now 100 errors rather than 1) and the inability to encapsulate (a structure type can't model private members, of course). There was a lot of research in addressing these problems in the 90s, but for some reason none of that work has made it into any mainstream structurally typed language today.
Even in functional languages names are used all the time with a huge amount of encapsulation behind them. They just don't have the benefit of a type system to manage that.
Could you elaborate a bit here? I've searched for solutions in this area before without much success. I'd be interested to hear more.
I'm coming to the opinion that, if you need private member functions to retain encapsulation, that's a strong sign that your design was never properly encapsulated in the first place.
The motivation for making something private is to prevent people from breaking your object by messing with things they shouldn't. Generally speaking, only real way for it to be possible break an object is if its internal state is complicated enough to allow that to happen in the first place. Which is itself a failure of encapsulation.
Somewhat more concretely: If you've got to private fields A and B, where any change to A must be accompanied by some corresponding change to B, and the logic for enforcing that rule needs to be enforced at several places within your class, then, at the level of abstraction your object is operating at, A and B represent a single logical entity, and should never have been represented as individual fields in the first place. They should have been encapsulated into their own object.
Even more concretely:
Don't do this:
class Thing {
val counter = 0;
fun foo() {
val x = counter;
counter++;
...
}
fun bar() {
val x = counter;
counter++;
...
}
}
And don't do this: class Thing {
val counter = 0;
fun foo() {
val x = counter_next();
...
}
fun bar() {
val x = counter_next();
...
}
private fun counter_next() {
val x = counter;
counter++;
return counter;
}
Do this: class Counter {
int state = 0;
fun next() {
val x = state;
state++;
return x;
}
}
class Thing {
val counter = Counter();
fun foo() {
val x = counter.next();
}
}
Very abstractly: Composing objects is the core idea behind OOP. In general, the more object-oriented design is the one that favors object composition over other language constructs whenever possible.Also, this is all arguably a separate issue from using privates simply to avoid cluttering your public interface when factoring code. If you're working in a language that doesn't really have good nested functions, private methods are your next best choice.
Instead of 'private member functions' my mind read 'member functions'.
For instance, I'd love to be able to create functions that operate on a Vector3 interface type in Typescript, but don't have the clunky syntax of
> AddVec3(AddVec3(v1, ScaleVec3(v2, DotVec3(v4, v5))), v4).
Classes in Typescript solve this just fine, so that I can do
> v1.add(v2.scale(v4.dot(v5))).add(v4)
But then I lose the benefits of structural typing. I still have yet to find a nice solution here.
I'm developing a structurally typed language now. Is there some research you could recommend me?
There can only be widespread hate for things that are widely used.
So much that you'll have people advocate doing inheritance using interfaces and composition which results in exactly the same thing with all the same pitfalls but at least it's not "inheritance".
That's the issue with these kinds of topics. For me, it's the other way round. Having worked with golang on significant code bases, the way they handle interfaces is just terrible IMO. It's been quite friction heavy, and loses out on one of the most important part of nominal typing: being able to use interfaces to tag classes. The hacks you see in golang to get around it, while introducing dangerous behavior, is quite astounding.
TIL it has name! And I share your opinion, it's much easier, especially when doing immutability due to property spread, etc.
The obvious takeaway here in some respect is gradual typing is better and nominal typing is annoying for little gain.
What if the true answer were you need a more flexible and powerful type system.
I think that moderate so-called "pragmatic" languages are the worst experience because of their lack of guiding principles.
Go find some old C code for doing something like compressing a file. You'll likely find a set of functions where the first or last parameter is always a pointer to some struct or array with the current state of the compression process. If you converted that code to C++, and made it an object, that pointer would be the "this" pointer. The support for objects is just recognizing the common pattern.
Inheritance is similarly recognizing that a lot of code had structs or arrays with multiple function pointers.
The language support just makes those patterns a bit cleaner and less error prone.
There's nothing you can do with OOO that you can't without it, and vice-versa. At the end of the day all we have are instructions and data.
Even when you elevate a particular construct to be first-class in a language; the expressiveness of languages differs greatly.
For example, prior to lambdas, first-order functions existed in Java in the form of static methods - but higher-order functions weren't really possible. Even now, we lack the ability in Java to express certain forms of functions which are possible in more expressive functional languages.
Lisp's in general have the same control constructs as many imperative languages, but it's possible to do higher-order abstractions over them through the use of macros and constructs like shift/reset or call/cc and the fact that everything is a s-expression.
In this regard, Java and C-like struct pointers are almost first-order OOP languages - the composition via inheritance is quite frankly poor compared to the other paradigms.
I see you have never used Swing. The reality is that when you needed a callback there were interfaces with a single method and you created anonymous classes that implemented that callback and its method. It's clunky but it's equivalent to higher order functions.
Whether they are "equivalent" depends on your definition of equivalency. Could Java pre-SAM claim to have first-class functions; I would argue not because there was a separation between methods and classes - even though you could make a case that by having `Callable` or whatever anonymous classes you could use to emulate similar behaviours. Does Haskell have inheritance? No, but you can emulate some parts of it with a mixture of template haskell, record types and lenses.
void mything_somefunction(MyThingStruct *mything, ...args){}
The weirdest thing was some of the crusty C guys in my group saying "Eww, is that objects?" No, it's maintainable, and it avoids collisions.It's even more strange now, that we have distinctly non-OO languages that explicitly use the first-parameter-is-struct pattern, e.g:
struct definition: https://github.com/elixir-lang/elixir/blob/v1.10.4/lib/elixi... and functions: https://hexdocs.pm/elixir/Date.html#functions
That's the origin of COM. It's basically the C mything_mystruct pattern with a lot of additional stuff on top. Actual COM objects were usually implemented in C++ though.
In OOP you can have multiple functions that are called "save" and they are primarily distinguished by their type. If you define a static function in Java you call it by invoking its namespace:
MyThing.somefunction(myThing, args)
Alternatively your function is bound to an instance of an object. Then the save function is chosen based on the type of the variable you are calling the function on.Compare this to Haskell records. Field names are not defined inside a type dependent namespace and therefore you cannot reuse field names in a second record. If I remember type classes correctly they allow you to define implementations sharing the same function name, but they don't allow you to have multiple type classes define a function with the same name so they do not offer type dependent namespaces either.
OOP tends to make organization easier because of this namespacing feature. It probably has led to "over-organized" codebases though. Too much of a good thing can also backfire.
Sure you can, it just has to be defined in a separate module (because Haskell namespaces correspond directly to modules). It's still annoying that types don't introduce a separate namespace but it is merely annoying.
That is the thing, at their basic level, objects are just some form of extensible modules.
Yegge's anti-OOP rant is mainly about this design in Java. Norvig's presentation on design patterns points out that some OOP tropes are compensating for missing non-OOP features like first-class functions.
Python's standard library is a good example of how to use OOP effectively. For example, `multiprocessing.Process` is a class because it explicitly manages state.
There are two biases skewing perception, too: the first is simply that people using other languages or writing newer Java well are probably not writing blog posts about how their language is less hassle than the business.
The second is that OOP is so much more popular that people forget to adjust for things like skill levels for a mainstream versus niche language. There are tons of jobs in OOP languages so there’s a ton of code written by people who weren’t going to write great code in any language, whereas a lot of less widespread languages - especially those built around an academic concept - will self-select programmers who are trying to improve their craft. This is telling you more about the job market and how much companies value quality than the languages.
There's also not a huge benefit to having data.sort() vs Array.sort(data).
OOP is a lot more than that. One can argue its great, one can argue it sucks, but either way, a bunch of methods grouped together isn't really what is being debated.
Eg: in JS you could use a symbol to store that state that is only known to the methods allowed to handle he array "internal" state. (JS isn't a great example because there are ways to get access to those hidden symbols when you shouldn't, but it doesn't have to be that way)
The benefit of `data.sort()` is you could write that in a function that accepts a `data` that could be an array, a vector, a linked list, etc... anything that implements the "sortable" interface. Writing `Array.sort(data)` in your function means the function won't be usable for any kind of data other than arrays.
The other features of OOP like encapsulation, inheritance and abstraction -- these aren't so special. But OO polymorphism is pretty unique.
That said, I do sometimes confuse myself trying to use them in Java, e.g.
void foo() {
Fruit[] basket = new Apple[] { new Apple(), new Apple() };
handleFruit(basket[0]);
}
void handleFruit(Fruit fruit) {
System.out.println("Do I get called?";
}
void handleFruit(Apple apple) {
System.out.println("Or do I?";
}
If it were runtime polymorphism I'd expect it to dispatch to the apple method, but it dispatches to the fruit method.I also run up against friction trying to define, e.g. the "mappable" interface across these two guys. Can't seem to get it done.
class Optional {
<U> Optional<U> map(Function<? super T,? extends U> mapper);
}
class Optional {
<R> Stream<R> map(Function<? super T,? extends R> mapper);
}
For what it's worth, Java went with the polymorphic Collections.sort(data) approach, not the polymorphic data.sort() approach.Since java 8, java.util.List had a sort method and Collections.sort(List) just calls List::sort. Before that, only the Collections.sort method existed.
At least from java 6 on, java has a SortedSet and SortedMap that accept comparators as constructor parameters, but have no sort methods (So basically data.sort, not collections.sort(data)).
Arrays can of course only be sorted via Arrays.sort, since arrays have no methods.
In Java, you can implement your own dispatch table quite easily
Map<Class<?>, Consumer<Fruit>> dispatchTable = Map.of(Apple.class, fruit -> handleFruit((Apple)fruit));
Fruit fruit = basket[0];
dispatchTable.get(fruit.getClass()).accept(fruit);It is just somewhat cumbersome.
It's not. Julia uses multiple dispatch so that you can use the same * operator on integers, floats, complex numbers, quaternions, matrices, matrices of quaternions, galois field elements, and then it's sophisticated enough that if you implement a new type with +, -, /, etc. you can get matrix solving from the standard library for free. So you just implement + - * / on the GF(256) and you can do reed-solomon erasure repair, hell, in quite optimized code trivially without rewriting LU decomposition.
Other FPs have solved the polymorphism question in their own ways as well (protocols are a common scheme).
Perhaps the author did not wish to admit that even back in 1980, "you [could] have your class Cake and EatCake() it too".
[:each :aCake | each eat: aCake]I think when everyone talks about OOP today they mean the lineage that descended from C++, which broadly is: Java, Javascript, C#, Python, and Ruby, to a lesser extent Go.
Contemporary OOP is the enforced semantic association of data with their methods, encapsulation of private data, and shared memory.
what are the big differences that make ruby more algol-like?
and conversely, what are the smalltalk features that make it less so?
Having used all three of the named languages, I disagree. Smalltalk has a lot more in common with Ruby than Erlang.
> I think when everyone talks about OOP today they mean the lineage that descended from C++, which broadly is: Java, Javascript, C#, Python, and Ruby, to a lesser extent Go.
Ruby, as you yourself noted, descends from Smalltalk, not C++. JavaScript descends from Self, not C++. Python doesn't really seem to descend from C++ either. I think you are confusing having (very loosely, in some cases) Algol-derived syntax for being descended from C++.
> Contemporary OOP is the enforced semantic association of data with their methods, encapsulation of private data, and shared memory.
I disagree with this description; shared memory is orthogonal to OOP, and encapsulation isn't even provided without awkward gymnastics in some of the contemporary OOP languages you (erroneously) describe as C++ descendants (e.g., Python has a unenforced convention for notating that a member should be considered private, and Ruby has private methods and makes instance variables essentially private, but also includes encapsulation-busting methods near the top of the inheritance heirarchy, like Object#instance_eval.) And without strict encapsulation, enforced syntactic association of methods with data also doesn't exist (not sure what enforced semantic association is supposed to mean, unless it is enforced by way of enforced syntactic association.)
I think the first one feels a lot more natural. It also seem to lower the cognitive load of knowing where to find the sort method / function. Is that just because I have used mainly OOP style languages?
Probably slightly harder is sort(data) in a language like Julia. The sort method wouldn’t be located inside the class definition and I’d have to run a command like @which sort(data) to find out where sort is defined.
All three would be trivial with a decent IDE though.
Pike's quote from that interview:
(...) let me say that I'm not much of a fan of object-oriented design. I've seen some beautiful stuff done with OO, and I've even done some OO stuff myself, but it's just one way to approach a problem. For some problems, it's an ideal way; for others, it's not such a good fit.
Here's an analogy. If you want to make some physical artifact, you might decide to build it purely in wood because you like the way the grain of the wood adds to the beauty of the object. In fact many of the most beautiful things in the world are made of wood. But wood is not ideal for everything. No amount of beauty of the grain can make wood conduct electricity, or support a skyscraper, or absorb huge amounts of energy without breaking. Sometimes you need metal or plastic or synthetic materials; more often you need a wide range of materials to build something of lasting value. Don't let the fact that you love wood blind you to the problems wood has as a material, or to the possibilities offered by other materials.
The promoters of object-oriented design sometimes sound like master woodworkers waiting for the beauty of the physical block of wood to reveal itself before they begin to work. "Oh, look; if I turn the wood this way, the grain flows along the angle of the seat at just the right angle, see?" Great, nice chair. But will you notice the grain when you're sitting on it? And what about next time? Sometimes the thing that needs to be made is not hiding in any block of wood.
A fairer analogy would be to say that OOP is a particular architectural style, and FP is another.
You can mix Japanese woodworking with European woodworking by using screws, nails and glue whenever you feel like it. Its simpler than pure Japanese woodworking, not as pretty, quicker to construct, doesn't last as well. You can even forego Japanese woodworking entirely, but to do so is to throw away an entire culture's worth of knowledge about how to cut, shape and join wood, so likely not a great idea.
In order to build reuseable components you need components that compose well. Object composition does not allow for composition without specific glue code for each integration.
For example:
functions addOne and addTwo can compose to form addThree with a single general function composition operator, no glue code needed and no need for addOne or addTwo to be aware of each other. (addThree = addOne . addTwo)
OneAdder and TwoAdder cannot compose without specific glue code or specific interface code to allow for DI. ThreeAdder = composeAdder(OneAdder, TwoAdder) would be implementation specific. Additionally ThreeAdder(TwoAdder, OneAdder) makes ThreeAdder aware of TwoAdder and OneAdder. These are not qualities of reuseability. The existence of a mutating object breaks reuseability.
OOP does have it's use cases but the hate does comes from somewhere. One of the main downsides of OOP is the fact that current practices makes it one of the least reusable paradigms ever. DI and object composition are illusions. That being said it's not all that bad... not all business apps need the form of extreme reuseability that FP provides, so OOP is often good enough.The point of the example wasn't to talk about naming. It was to talk about a fundamental structural problem with OOP.
"There are 2 hard problems in computer science: cache invalidation, and naming things"
or its cousin:
"There are 2 hard problems in computer science: cache invalidation, naming things and off-by-one errors".
But OOP languages do have lambdas and function pointers, so can easily do something very close to that. In Kotlin:
fun addOne(value: Int) = value + 1
fun addTwo(value: Int) = value + 2
val addThree = fun (value: Int) { addOne(addTwo(value)) }
There are libraries that add a pure functional composition operator via a + operator overload, I think.Even in Java you can do function composition, though it's not well known and a bit verbose:
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
static int addOne(int i) { return i + 1; }
static int addTwo(int i) { return i + 2; }
void combine() throws Throwable {
var lookup = lookup();
var type = methodType(int.class, int.class);
var first = lookup.findStatic(Main.class, "addOne", type);
var second = lookup.findStatic(Main.class, "addTwo", type);
var combination = filterReturnValue(first, second);
System.out.println(combination.invoke(1)); // Prints 4
}I, personally, am a believer in minimalism. Features which experts tell beginners to avoid should be removed. That's very hard to do with programming languages that need to support legacy code though. So I always look for new programming languages that try to do things differently, with as few features as possible.
Analogy: You talk to some master chefs. You tell them that the sharp knives are dangerous; they should get rid of them. They're going to tell you to get lost. The knives are valuable to them precisely because they are sharp. Sure, an amateur like me could seriously cut himself on one. So?
I don’t think your analogy is appropriate though. My original analogy, the Swiss Army knife, conveys what I think is the core of the problem with OOP: it takes unrelated, powerful tools, and ties them together in a way that makes them unwieldy and more difficult to use.
This problem is inherent to the object itself. It ties together unrelated concepts: polymorphism, data abstraction, mutable state, inheritance, and modules. Hence the Swiss Army knife analogy.
Other languages (namely functional languages) give you all of these powerful tools but you get them separately, so they’re easier to control.
OK, "I wouldn't work on such a project" is a fine answer. But not a universal one.
1) performance bottlenecks are generally no longer CPU computation (network tends to dominate for many many things), and there is nonzero overhead to most functional operations.
2) coordination-free concurrency can in many cases often overcome computational bottlenecks, and non-OOP is far more effective for the code author in dealing with/grokking concurrency.
3) in the intervening time, functional patterns for some things (like UI) have been discovered which make fp a good fit for things that in the past were understandably the domain of OOP.
I, however, often see objects used in bizarre ways. Often to reduce written lines, or to take a concept, and attach another concept to it in a fairly ad hoc way. This is never helpful.
The most problematic times i have with object is when the concept behind them become too general. E.g. If a have a program with a deck of cards, i can have card objects, with rank and suit, and i can have deck objects, with 52 cards, shuffle and deal methods. That's fine, but if i have an object that's just "poker game" that tries to shove all those concepts into one object, it seems to become very unwieldy, very quickly.
This is painful use of OOP (actual code from an Android app):
Camera.Parameters params = mCamera.getParameters();
params.setRotation(90);
mCamera.setParameters(params);
This would be NICE use of OOP: mCamera.setRotation(90);
OR mCamera.setParameters({"rotation": 90});
This is painful use of OOP: BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
mImageView.setImageBitmap(bmp);
This would be nice use of OOP: mImageView.setImage(new Bitmap(data = data, sampleSize = 4));I very much like the Pythonic paradigm of conciseness, e.g. PIL.Image.open() or cv2.imread() with optional arguments which is light-years more pleasant to code with than the BitmapFactories and BitmapFactory.Options and AbstractSingletonProxyFactoryBeans.
It's also really nice that Python and JavaScript classes/objects can have arbitrary attributes stuck onto them, e.g. someObject.__source = "foo" or set and that sticks to someObject wherever it goes. It's a very intuitive way to implement a lot of things; one can liken it to sticking a post-it note on a box.
These sorts of objects provide a nice API, but they don't contribute to good internal designs. I have seen many systems were there is an attempt to map objects to physical things (like on a modem, you might have a "demux" object). Invariably, software ends up having to bend to the design of the hardware, and you end up with very leaky abstractions. Why have a "demux" object, when all you are trying to do is write some registers depending on user configuration? Why not design your software around what it is actually doing, rather than what the system's overall purpose is?
Interfaces are a different question. High level interfaces that reflect what the system actually does are very useful. I think this is born out in some modern OOP-ish languages like Go or Rust where interfaces are everywhere, but full scale OOP is mostly absent.
What I don't like is when things like options, parameters, and intermediate representations of data that aren't actually intuitive are represented as instantiated objects of their own. In some sense I expect every instantiated object to "do" something useful of its own, and represent a functional block in a flowchart. Objects should, to the greatest extent possible, represent "is" and "has" relationships in a system, IMO.
Also, class hierarchy should be well designed. For example, an Exception called ConnectionError (that inherits from Exception) is great. It can be parametrized from there. What sucks is if there is SomeFooConnectionError, SomeBarConnectionError, SomeBlahConnectionError that all inherit directly from Exception and don't inherit from a common ConnectionError, because that makes the try/catch block super verbose and easy to miss a possibility.
Camera camera = createCamera({"rotation": 90});
If you need another camera with different parameters then create a new one. You can build the options before the creating the cameras to avoid duplication. Bitmap bitmap = createBitmap({data: data, samepleSize: 4});
ImageView imageView = createImageView(bitmap);
In the case where you are mutating image view, it's an optimization to reuse the same memory that could be handled by your language/framework so you don't have to think about it. For example, like with react it could create an internal representation of what is rendered and then diff it with the new rendering and reuse the internal representation of image view and mutate it for you so you don't have to think about doing the optimization yourself.There's a trade-off being made between conciseness on one hand and fast feedback on the other. Python leans toward conciseness and Java/C++ lean toward fast feedback. In Python you can express complicated ideas quickly, but you can't necessarily express correct ideas quickly. Whereas in C++ you can express correct ideas quickly but you can't express complicated ideas quickly.
If it's costly for you to correct defects in deployed software then you might prefer the fast feedback of C++ or Java rather than the flexibility of Python.
String string = new String("string");
isn't enough repetition, and so they take it a step further: StringFactory mStringFactory = new StringFactory(StringFactory.FOO || StringFactory.BAR);
StringOptions stringOptions = new StringOptions();
try {
String string = mStringFactory.createString(StringFactory.STRING, "string", stringOptions);
} catch (java.lang.string.StringException e) {
runOnUIThread(
new Runnable() {
try {
// facepalm
I'm exaggerating ;) but as a Python programmer myself, this is what comes to my mind whenever someone says the word "Java" to me. Obviously not for strings, but for more advanced constructs (e.g. Bluetooth) I often do see this much repetition in Java code. It's almost like someone designed this language to prank engineers.BTW You missed:
public static final String STRING_STRING_CONSTANT = "string"; some_dict = module.scrape_api()
value_I_want = some_dict['my_value']
I gave up after a day, when I just wrote down exactly what I expected to pull down and what types were in the values and keys. I can understand it helps you prevent errors but man is there a learning curve... BTW, I could write those checks in Python as well, but they are optional.You're probably going to need to check `value_I_want` though. So maybe the checks aren't quite as optional as they look.
value_I_want = some_dict.get('my_value', default_value)
If you want a more strict check including type checking: value_I_want = some_dict.get('my_value')
if type(value_I_want) is not str:
do_something()
In Java the real problem is nested JSON objects -- it's a real PITA to read those. You can't do something like this (Python): value = some_dict.get("foo", {}).get("bar", {}).get("blah", default_value) value_I_want = some_dict.get('my_value':str)
and value = some_dict["foo"]["bar"]["blah"]A rotation is a gravity based human concept that results in the transformation of stream data before being displayed, not anything related to the actual camera.
camera_set_rotation(camera, 90);But sometime it is too verbose to use OOP, for example lambda vs anonymous listener. The biggest problem is it is easy to do wrong abstraction with OOP style, for example, store state that make no sense and put weird method inside a class
Everyone I know loves OOP.
I am primarily a Ruby developer so I know I'm in a bit of a bubble but still... The title is pure click bait.
The same thing happens with FP and OOP. Both are too broad to be defined concisely, and the definitions, when collected, are often contradictory or necessarily exclude what others would consider valid <paradigm> languages.
Any discussion about these paradigms and their relative merit or "goodness" has to be clear about which subset of OOP and FP properties are being discussed and compared.
An example: A popular view of many current FP proponents is the idea of immutability as a key requirement of FP. But this necessarily excludes Common Lisp, a multi-paradigm language with the ability to use many other FP ideas. Instead, CL would be included under FP (or include FP as one of its paradigms) by someone focusing on things like higher order functions, being able to construct functions at runtime via closures, etc.
The same can be used to understand OO. Message passing is a property of some OO systems. Inheritance and class hierarchies are a property of others, possibly overlapping with the first. Closely tying methods to classes (see Java) is another common property. But Java and Smalltalk are both considered OO and both contain ideas absent in the other. Which of those ideas are OO, which are ancillary. And which OO ideas are present in one but absent in the other, does it invalidate the other language as an OO language or does it just mean it has a different view of OO?
[0] https://en.wikipedia.org/wiki/Definitions_of_fascism#Umberto...
OOP on the other hand is very poorly defined. There is a tight little cluster of languages which are commonly called OOP, but the defining features of that cluster are generally negative so "OOP proponents" began arguing that while those languages are OOP, the things that unite them aren't actually defining characteristics of OOP. Advocating for other regions in this space for which to apply that label is problematic because they either exclude other traditionally-OO languages or the described region overlaps too much with the "functional" and "data-oriented" regions (one can retort, "per your definition, Haskell is also an OO language"). So in their loyalty to the term, "OOP" proponents instead make arguments about what doesn't define the language (note that not all proponents make all of these claims):
* OOP may or may not involve inheritance.
* OOP may or may not involve message passing.
* OOP may or may not involve a high degree of spaghettiness in object graphs
Ultimately they'd prefer the term be semantically useless than useful and negative.
Ironically, the only thing that unites all OOP advocates is a fondness for the term.
I do invite OOP proponents to prove me wrong by coming up with a common, semantically useful ("meaningful" in the formal sense of the term) definition that describes all traditionally-OO languages and generally excludes most data-oriented and functional languages.
Note that a common rebuttal is that you can write bad code in any language and with any paradigm, which ignores that the claims are about the frequency and not a binary proposition about whether or not the language precludes a particular anti-pattern. The extent to which a Java program doesn't exhibit the criticisms above is the extent to which that program is not OOP, and very probably data-oriented or functional in nature.
There is also a big difference if you're working mainly on the backend where you shouldn't keep any state or the frontend to how useful many OOP concepts are to you.
In meatspace, everyone I know is fine with OOP. Usually it is other aspects of the programming languages that result in griping.
I have seen people write entire systems in DOS batch script. Was that a good idea? Not really. Can it be done? Oh very much so. But is that the fault that DOS batch script let you do that? Not really.
OOP is the same sort of thing. It is a tool. You can take the abstraction too far and make it miserable to use and extend. It depends on the team/person doing it. Perhaps they are fine with 'too far' the next poor soul to come in may not be able to keep up with the 13 layers of abstraction that they had no decision in. Where is that line? That is more art than CS as the rule is 'it depends'.
Seeing code that goes all in on OOP absolutely makes me cringe as I know it generally means the unit tests are going to be more complex than they would be in an FP environment, the code's likely going to be harder to extend as there's no amount of class inheritance planning that can prepare you for all future business requirements, and in most cases it's just going to be so much more verbose than an FP solution would be (I know this last point is a bit of a personal issue).
Code that goes all in on FP I absolutely love on a pure nerd level, but absolutely hate on a practical level. Pure FP is neat in the same way that seeing someone who can calculate out pi to a hundred digits is neat. Sure it's impressive, but like... I got this calculator that will handle it much easier.
Inheritance got caught up in the "is-a" mindset of 1980s AI, and turned out to be only marginally useful. Being able to have an object as a field of another object is almost as useful. A point I make occasionally is that there needs to be a good way to find the owning object when you do that. Is there a language where, when B is a field of A, you can find A from B? I've plugged for syntax for finding the owner in Rust, where backpointers are difficult.
Order of initialization for inherited objects is a huge headache. If B inherits from A, what in A is B allowed to call or reference during construction? This sort of thing needs language support. Same for order of destruction.
Multiple inheritance is usually a mistake. If you reach the "diamond pattern", your code is too cute.
I've built this multiple times in Python in a few different ways. Metaclasses work (for e.g. declarative stuff, like in ORMs and data description), the descriptor protocol works for the runtime kind (caveats ensue).
> Multiple inheritance is usually a mistake.
Allowing multiple inheritance seems to work better in some languages than others. For example, in C++ it almost always seems to be a bad idea. In Python it tends to work out more often (usually when the other classes are pure mix-ins).
I'm not sure what language you are talking about where you have "one set of data", maybe BASIC?
There are almost no modern languages save perhaps Javascript that rely on global data extensively. (and even modern Javascript has moved away from use of global variables).
Modules usually have data types that can be instantiated via functions/procedures in the module that let you avoid global data. They don't have to have data fields in them that are changed directly by the procedures (giving you, effectively, singletons).
The Hibernate ORM framework for Java supports this paradigm.
It's not unusual in system/kernel C/C++ code to do that with offsetof() macro.
Most mainstream OOP languages support it in some form, and it is quite useful, specially when considering single responsibility principles.
Like everything else, it can get abused.
OOP works on low complexity, small projects, but quickly falls apart at certain point, but then it's just head scratching and finger pointing ("this ORM is just bad, next time we will use a different one").
More experienced people figured out some way around OOP to make it more robust, so whenever OOP is criticized they just do the "oh, you just doing it wrong: insert excuse and advise here".
People who really though it through, and tried different approaches are the ones that hate it and are vocal about it. They see through the folly. They can tell how core principles of OOP are bad ideas. How encapsulation at object level is usually pointless and costly, how inheritance doesn't make sense, and so an so.
...pretty sure there are competing abstraction layers to working with DBs other programming paradigms.
> OOP works on low complexity, small projects, but quickly falls apart at certain point, > More experienced people figured out some way around OOP to make it more robust,
I thought it fell apart?
> "oh, you just doing it wrong: insert excuse and advise here".
Wait til you learn what point-free style is in Haskell! Have fun!
> People who really though it through, and tried different approaches are the ones that hate it and are vocal about it.
So if you happen to like OOP, you just haven't thought it through. Thanks for the constructive comment!
>> Wait til you learn what point-free style is in Haskell! Have fun!
I honestly don't understand what you're trying to say here. Care to elaborate?
As for ORMs: no, other styles of programming don't have this particular problem nor competing abstracting layers approaching this level of chaos. The ORM addresses a specific mismatch problem between relational databases and objects from OOP. This problem is so messily unique that it has been aptly named "The Vietnam of Computer Science": http://blogs.tedneward.com/post/the-vietnam-of-computer-scie...
This kind of works (to the point) while the data is entirely in memory, but quickly falls appart when it's a bigger data set in external storage.
I am yet to see a OOP project with ORM that have not fall appart in a way I described in these twitter post:
Just saying "if you are really smart you'll see that oop is bad" just sounds so... Empty.
Traits feel more natural to me. Instead of needing to make a subclass of a data structure to attach a foo method to it, you can define a collection of foo methods with the same name and pick the runtime implementation based on data structures passed to the method. I understand that this six of one, half dozen of the other, but I do prefer it this way.
Granted, this is not something OOP does "wrong," and you can use generics to accomplish a similar thing, so it's not that big of a deal.
I fully accept that not everything benefits from being expressed in objects, which is why hybrid languages like Python are so excellent. But yes, even if some things don't map, keeping OOP around for things that do makes too much sense to ignore.
It used to be GUIs until React, Elm and such proved that FP style describing the end state is a better fit.
"Simulations" can be done in both real time state management OOP style or FP or data centric style with explicit time.
Operating systems are a good example for state management OOP. Linux is written in C with OOP techniques. FP with immutabilty of objects would likely not be a good fit because operating systems are expected to just run in thr background and to not waste resources (eg by copying full objects each microsecond).
Anything where Erlang shines is another good fit for OOP because its actor model actually is the original vision of OOP - objects reacting to messages.
* "Everything is an object". This creates unnecessary confusion between data and modules and sometimes even functions.
* "Everything is a message". This encourages splitting state between a myriad of objects, each hiding some tiny bit of mutable state that changes whenever it receives a message. Reasoning about such a state graph is impossible for any non-trivial application.
* "Everything is an interface", which leads to tests tightly coupled to implementation due to mock over-usage.
* "Everything is an inheritance hierarchy". Practice has proven that composition is better than inheritance.
Everything is a pointer-wide cell of bits, with a type tag.
Now we're cooking with gas!
OOP only provides illusions of reuse-ability. In reality every component created in OOP takes extra steps and extra glue code to make compose-able.
In programming you want to build small primitives that you can compose to form larger higher order primitives. This allows you to reuse lower level components to form new programs. OOP does not allow you to do this easily.
An object is a grouping of primitives. So according to your logic OOP should have the same problem.
The real issue isn't grouping. The true nature of abstractions is the formation of higher level abstractions from "groupings" of lower level primitives. So "grouping" is fundamental to abstraction and therefore fundamental to programming.
The problem with OOP is that the groupings are forced. I cannot reuse a method outside of the context of the object because the method is tied to mutable state.
For example:
Try to write a function addOneTimesTwo from the composition of the three primitives below (in python):
def addOne(number):
return number + 1
def timesTwo(number):
return number * 2
def compose(f, g):
return lambda x: f(g(x))
Answer:
addOneTimesTwo = compose(timesTwo, addOne)You will note, compose is a universal functional operator. The definition for compose above is the most primitive way to compose functions in FP.
Try to do the same with OOP:
class AdditionObject:
def __init__(self, number):
self.number = number
def addOne(self):
self.number += 1
class MultiplicationObject:
def __init__(self, a, b):
self.a = a
self.b = b
def mult(self):
return self.a * self.b
Certainly do-able with OOP. But I cannot extract the methods away from the state. In order to use the banana (method) I need the gorilla holding it and the entire forest it lives in (Object) to come with it.It can be done, but generally you need a lot more glue code to make it work especially given the fact that people can get more creative with OOP. As you can see the MultiplicationObject is an example of this creativity. It was written with a different interface then the AdditionObject.
Basically if you know haskell even similar issues to the above can be dealt with in terms of haskell. You can certainly define a composition operator for even the most complex types. The issue with OOP is that EVERYTHING is a complex type. A standard Object already has enough complexity to necessitate custom composition operators so you're basically writing custom glue code everywhere. Either you're writing complex glue code or your bending all your code to fit into a framework.
Nothing is truly modular.
class Math {
public static function addOne (int $i){ return $i+1; }
public static function multiTwo function (int $i){ return $i*2; }
}
With classes those functions or methods are grouped together under a math class.Calling it is as simple as: Math::addOne(2)
When you need Math you include it.
Seem cleaner than including little addOne functions as global closures.
I'm the programmer that has to compose these classes... You're telling me that I have to change the implementation/interface of what they wrote? What about the dependencies?
This is what I mean by "Not reusable."
Additionally typical OOP involves mutating state. The purpose of the class is to hold methods that operate on internal state. Otherwise what you're writing is basically function calls under a namespace.
>Seem cleaner than including little addOne functions as global closures.
Again Put it under a namespace called Math; problem solved. The purpose of a namespace is to group functions together. The purpose of a class is to group methods together that operate on shared state.
If your methods don't involve a "this"
function addOne(){
this.value + 1;
}
then you're not really doing OOP. You're just repurposing the class as a namespace.The great thing about repurposing a class as a namespace is I can pass that repurposed namespace package anywhere.
You never need to change their code either inject those objects into a new class or extend one and inject the other.
namespaces can be passed anywhere anyway.
>The number + 1 or x 1 doesn't require an object or oop. It doesn't require a function either and would be better to just inline.
+ 1 and x 1 are just examples. If you take the examples and make it more complex the problem still remains.
An inlined expression is still a function. It's just not named.
y = function(x){return x+1;
b1 = 1 + y(2)
x = 2
b2 = 1 + (x + 1)
In the above code y(2) and x = 2; x+1; are equivalent.>You never need to change their code either inject those objects into a new class or extend one and inject the other.
You rarely ever need to do this in FP, but you need to do this all the time in OOP.
ORMs that overload getters and setters as DB transactions: It creates a harmful illusion that the data inside the object are equally as authoritative as the data in the source of truth (as in database).
See https://twitter.com/dpc_pw/status/1240719977071575040 for how this works in practice.
People like to call this a leaky abstraction but I think it's far more boring. Know your tools (both the ORM and the database). It will be worth it. Don't hesitate to use raw SQL if the ORM doesn't play along.
OOP is 'widespread' because it is frankly a very powerful (and obvious) way to encapsulate and organize information and function by breaking down said problems into digestible units or 'mini modules' if you will, that also happens to lend well to how humans interpret the world.
Polymorphism is a very powerful tool for adaption.
Far from 'problems at scale' - it's the complete opposite: well designed OOP is generally the best abstraction to use at scale, to the point wherein most of the world's most comprehensive APIs are in fact OOP.
Inheritance is definitely overused, and OOP is not ideal for many kinds of projects, but overall, it's successful because it's extremely useful.
It's 'mid level' Engineers, those who have discovered the foibles of OOP Orthodoxy, who 'develop their own ideas for the first time' who are the most ardent critics. Those who get past that see it for what it is, just a tool, very useful in many ways, not so much in others.
TypeScript has fundamentally better OO utility that Javascript, Dart was developed using OOP, Kotlin absolutely kept those ideas, etc. etc..
Obviously, maybe not the best for a lot of systems programming.
It's 100% here to stay, or at least an evolution of it.
Trait-based programming solves polymorphism much more elegantly as any type can adopt a behavior. These systems are going to replace the old paradigm.
They are definitely useful, but 'inheritance' is extremely useful in many cases wherein there is a lot of material overlap in code.
It gets out of hand fast and it's not perfect, but it's just so useful at a small scale, in so many ways.
You seem confident in this position - can you provide some resources for others to become more familiar with that philosophy, proofs against OOP, and/or alternative approaches to architecture?
- key point is that is that it's common practice, and the situation you're in is debugging data emitted by someone else.
The second link is good, but it's just describing OOP done right. If you read the original publications, way back, OOP was never about idiomatically programming in Things - the typical example of "Dog extends Animal" is antithetical. Objects should be closer to microservices. Within those objects... stateless transforms and simple state containers (like structs), or some spaghetti. Didn't matter, the other objects should never interact below the interface.
Can you explain these? I'd like to know why people think encapsulation and inheritance don't make sense.
As others have pointed out, everyone has a different definition of what the core principles of OOP are.
When Alan Key coined the term "object-oriented programming", he had message passing (among other things) in mind. If you like that definition, stop reading this and go use Erlang. If you were more thinking of the various modern languages we now call OOP, read on.
The "lies for small children" version is that OOP is all about modelling the real world in a natural way: apple.eat(), car.drive(), etc. Nouns are objects and verbs are methods. From my POV it's a strawman to reduce OOP to this, but I bring it up because some people like it this way and will argue for it - "It's a natural way to think about software", etc.
How are nouns and verbs actually modelled in practice? You don't file.read() and file.write(), you construct a FileReader and FileWriter, and you tell them to read() and write(). The verbs ofen show up in class names (fileREADer). It's delegation of responsibility and I argue it's a good way to design software. But delegation of responsibility is not restricted to OOP.
Putting aside modelling and design concerns, what are the core features of OOP languages? I like the Uncle Bob definition of Inheritance, polymorphism and encapsulation.
Inheritance:
Inheritance is fraught with more "lies for small children". A Lion is a type of Cat which is a type of Animal. I admit this is another strawman because using inheritance to model taxonomies like this is dumb and doesn't happen in the real world. How is inheritance used in the real world? I honestly don't see it that often. A lot of frameworks like to hijack your 'main' method and force you to use inheritance to run the thing, e.g. MyApp extends SpringApp. I keep trying to think of inheritance examples and think "no, that's just an example of using an interface".
Anyway, one of the principles in the OOP body of knowledge that has emerged over time is to "favour composition over inheritance". You might get to share some behaviour and cut down some code between 2-3 classes, but on the flipside, now when you want to modify just 1 class, you have to realise you're modifying the other 2 classes as well.
Elsewhere in the OOP body of knowledge is the five SOLID principles. I love SOLID. Someone familiar with SOLID might say "but we know how to safely extend classes because of the (L)iskov Substitution Principle!" But it's not an instructive way to safely use inheritance. It basically says "don't fuck up inheritance".
Polymorphism:
Lots of languages have polymorphism. I mainly use Java and I'm disappointed in its polymorphism story. Perhaps other OOP languages do it better? Java has interfaces/abstract-classes as well as "Generics" (first-order parametric polymorphism). First-order parametric polymorphism will let you model List<Integer> as List<I>, but it will not let you model it as L<Integer>. Why does this matter? When you want to abstract over things that are "mappable", like Optional and Stream.
<U> Optional<U> map(Function<? super T,? extends U> mapper);
<R> Stream<R> map(Function<? super T,? extends R> mapper);
I can't do this: <U> F<U> map(Function<? super T,? extends U> mapper); // The F stands for "Forbidden" !
I have worked on a large codebase where one of my tasks was to move from Guava Futures to Java 8 CompletableFutures. I really would have liked the ability to hide those concrete classes behind some kind of abstraction. Incidentally later on one of my colleagues said he'd like to rewrite it using Observables. At a different company one of my coworkers went the other way - he did a "hack week" project of converting a codebase from Observables to Futures. It would be better if you could just leave the business logic generic and instantiate it over an Observable or a Future as you saw fit. I don't know OO languages that let you do this.Encapsulation:
I think the most promininent definition of encapsulation is you hide data inside an object and only manipulate that object using its (public) methods. Why might you want to hide data in such a way? So you can delegate responsibility to the object, because it will handle the data better than you will from outside the object. You don't need objects for that. You can do that with data and functions - unless you insist upon the apple.eat() syntax instead of eat(apple) discussed in paragraphs 2-3.
NB. Putting state inside an object doesn't make it safe for concurrent modifications. But that's a whole 'nother discussion topic. In the "real world" I rarely need to use a mutable data field in an object. Fields are generally just other dependencies, e.g. database handles or other services.
I think I did a reasonable job of hitting "the core principles". It's not so much that they're "bad ideas", but rather, once you strip away the large, good majority of ideas which is available outside OOP (e.g. delegation of responsibility) you're just left with the bad ideas (e.g. inheritance) or ideas which have been executed better elsewhere (polymorphism, message passing.).
On encapsulation: I joke that encapsulation on a level of an object is like putting a lockpad on your right pocket to protect your left hand from reaching to it.
Normal people keep doors and locks at the entrance of a door or a room (module level), not on every single object they have. That's why OOP software tends to look so schizophrenic: with walls erected between everything against everything, making stuff like https://github.com/Hello-World-EE/Java-Hello-World-Enterpris... possible.
You might check some of my blogposts at dpc.pw if you care to read more of my thoughts on this.
OOP really hasn't been given a fair shake. On the academic side, people figured out pretty early on that OOP doesn't mix well with an imperative programming style. It undercuts the basic idea, same as for FP. But the siren song of multi-paradigm programming is hard to resist. So the tradition of OOP that started with C++ (and ultimately displaced what the academics were working on) was basically an experiment to see if you can improve the flavor of cow pie by mixing it with apple pie. It turns out, no, not so much. You don't really make the cow pie any better, but you do make the apple pie much, much worse. Unfortunately, few people realize it's even possible to do OOP without all the BS (pun intended). Most of us have never known anything but this tradition that basically ignored everything that happened during the 1970s, rolled the universe back to the state of the art as of Simula 67, and took over the world by reassuring managers that nobody was going to try and take away their precious lowest-common-denominator imperative code.
I mentioned elsewhere in the comments that Alan Kay once stated, in so many words, that to him encapsulation was about getting away from an imperative, stateful way of doing things and moving toward a much more declarative style of programming. Being able to do that was a key strength of his formulation of OOP. And it's hard to deny that he was on to something. What his lab was able to accomplish, in so little time and so few lines of code, should be enough to give anyone pause.
But OOP is now strongly associated with bloated and sprawling codebases, not smaller ones. And enabling people to rise out of the imperative programming tarpit and adopt a more declarative style is now commonly cited as a key advantage of FP over OOP. Clearly, something was lost along the way.
I think its more complex then this. The simplest form of programming is procedural. OOP is a level of abstraction above it in terms of understanding. I think the catharsis of grokking OOP gives it the illusion of being better.
Some very large project, like Windows and all major browser browser engines (as far as I know) are written in C++.
If anything is pure OOP it's COM. And although it has a deserved reputation for being hard to start working with, and documentation these days is sparse; it's going full-steam ahead when you boot a Windows box.
For example I just added a data caching system where a generic DataCache is used by most data types, with a few more sophisticated caches for special needs implementing the same DataCache protocols, so they can all plug in to the same places wherever needed.
And a series of CacheRefresh objects that inherit base refresh functionality from a superclass, and just impose the their specific REST api calls. And everything is written as functional as possible, with limited state and parameter dependent methods.
It’s working great, any model that needs caching can have a data cache, and any UI that needs to prefetch data can have a CacheRefresh object for each type of data it’s dependent on. I’ve pretty much minimized boilerplate and redundant code this way.
So what am I doing wrong, or what could I be doing better?
That statement seems out of touch of the decades of OOP flogging that it took to get OOP support to be a staple of mainstream programming languages, so that it could be a common denominator.
Just a some three decades ago, anyone using OOP terminology was instantly stereotyped as an ivory tower academic.
All we were ever taught was basic imperative programming, then OOP and then several years (!) of just OOP frameworks (Android, ASP.NET, Angular and the like). We never talked about functional programming, we never talked about software architecture in general (outside of those frameworks).
There was a subject where we explored "computer science fundamentals" like some algorithms and data structures and, among other things, logic constraint programming. But that subject was so small and unimportant to your grade that you could just sleep through it.
This is pretty vague. Is "school" here, like, a 4-year comp sci degree? A bootcamp? A 2-year diploma?
To the vast, vast majority of software developers, it's "just how you program" (most don't even know there's alternatives).
It's really hard to go in an organization where hundreds or thousands+ developers live or breath through OOP as the gold standard, and suggest to do it differently. Thus the status quo lives on.
I don't enjoy the functional languages out there, at all. I enjoy some functional concepts within an OOP language, but I would be fine with just a pure OOP language if I had to.
Then among the people who do know about it, an even smaller fraction learnt about it first, or in the same depth. Among those who did, very few learnt it in a practical way (Learning ML in college is unlikely to get someone to use FP to build production software, especially before ReasonML was a thing).
With all that, is it a stretch to think people don't use FP because they don't know about it? No, I don't think so. It could be 100% objectively superior in every way and people still wouldn't use it in those circumstances. And its' far from being "obviously" better.
Eventually I sat down and read what OOP actually was. I immediately realized that I'd never worked on a single object-oriented architecture despite a decade of working in object-oriented languages.
I can't stress this enough. Using an object-oriented language does not mean you are building an object-oriented system. If you cannot differentiate your classes from C structs, you are probably not building an object-oriented system. If you have functions that manipulate objects, rather than objects manipulating each other, then you are probably not building an object-oriented system.
This article seems to conflate these ideas in the same way. It seems to say that "the success of languages who happen to be OOP" somehow implies "OOP is still one of the dominant paradigms right now". I don't see any other explanation for that claim beyond this inference.
Based on all the systems I've worked on at various software companies (heavily weighted to SaaS), I would be legitimately surprised if it was true that "OOP is still one of the dominant paradigms right now."
edit: Decided not to explicitly recommend the Byte article[0]. It's good but doesn't go deep enough to get the nuances across unambiguously.
[0]https://archive.org/details/byte-magazine-1981-08/page/n75/m...
I think C would be 10x more useful language if it ditched include files and had "import" like python
You can mutate state on the inside, but still provide a "pure" mathematical function on the outside. For example, a root finding solver. The general problem/feature with objects, is that when a method returns, the object may be in a different state (and often is). Such state changes are hidden but not "encapsulated" as they still contribute to the global state of your application. In other words, the global variables are still there, you've just organised them into modules.
* ability to reference other files in a manner other than copy/pasting
- allowing the compiler to understand when it should cache what it already knows, instead of having to set up and deal with precompiled headers for large projects
- ability to treat a header file as a single unit and not leak preprocessor defines
* treating many files as a single compilation unit, leaving LTO to only be needed when linking to a library
Like you say pythons import is a different beast and it can run code at runtime when it comes across that statement. Some training is not clear on that. Like you point out import does not just import the file it runs the thing. If the file only contains def's then it effectively just defines a bunch of functions/objects and does nothing else But it can. C is not like that. It is more copy the whole file here and apply the current state of pre-processor rules.
I'm trying to imagine this, and I'm at a loss. How do you mean? The namespaced part of the import, where you can pick and choose what you get? Or the managed part of the import, where all your libraries live in a language-version-specific folder?
One of the great joys, to me of C, is it's insane flexibility. Include files are a hassle, but I don't feel they're something I've ever had to work around. I can't say the same for /usr/lib/pythonX.Y or in particular the way things like 'pip --user' work.
> I'm pretty sure a lot of the popularity of oop actually came from modularity
That's one area where C could be better, in my opinion. I don't need inheritance or friend classes or any of that, what I _really_ want is to namespace the methods that are meant to work on a single struct _with_ that struct itself.
Effectively, I would love to write my C structs the way C++ coders write the POD types.
To me this is the primary benefit (although the file mapping is convenient).
As to python... it is impossibly more flexible than C. I have done silly stuff like reading all the files in a directory, then i imported all the python files I found.
I think C is not very deep, but it is very well known.
> I think C is not very deep
Depends on what you're using it for, I suppose. For low level stuff I enjoy the fact that I can largely tell the shape of assembler from C code. What does Python really execute when I do `f = open('file', 'rb')`?
> The real significant productivity advance we’ve had in programming has been from languages which manage memory for you automatically. It can be with reference counting or garbage collection; it can be Java, Lisp, Visual Basic (even 1.0), Smalltalk, or any of a number of scripting languages.
https://www.joelonsoftware.com/category/reading-lists/top-10...
(Grep for "Automatic Transmissions Win the Day")
I do wish Java had made `final` the default for fields and variables. Lombok's `@Value @With` takes away a lot of the pain though.
The "dangerous" parts of the application are now highly visible and neatly separated from the harmless parts.
Immutability is more of a core FP concept, than an OOP one. I guess the point is the categorization of the language you're using doesn't necessarily commit you to programming in that paradigm.
Object-oriented programming, fundamentally, is about associating code with the data it manipulates. Code + data = object. Two essential follow-ons:
* Encapsulation: Your object provides an interface composed of methods, without allowing callers to reach in and mess with the data inside except by calling those methods. This allows your implementation to evolve without breaking callers.
* Abstraction/polymorphism: Objects with different implementations can implement the same interface thus making them interchangeable. This enables code which operates on such objects to be reused across wildly different use cases.
These fundamentals exist in basically all large systems. Example: Linux file descriptors are objects. Different kinds of file descriptors (e.g. disk files vs. sockes) implement the same interface (syscalls, e.g. read/write), and you cannot reach directly into their implementation from userspace, which allows the kernel implementation to evolve over time. You really can't not use OOP in these scenarios.
The opposite of OOP is separating code from data. In anti-OOP, you declare your data structure completely transparently, without declaring any kind of "methods" that apply to it. Code modules interoperate by operating on the same underlying structure. This tends to lead to sclerosis as adding new features to the data structure without breaking existing code can be quite difficult.
My hot take: The philosophy of REST (though less often the actual implementation) is disturbingly anti-OOP.
I'd also like to know why you think REST is disturbingly anti-OOP. It does impose a separation between the implementation of a resource and its representation, right? So multiple implementations could have the same representation and the same verbs. So in that sense, it's like OOP, isn't it?
Looking it up, I think I was wrong about that. Early OOP did in fact have implementation inheritance.
I guess what I should have said is, OOP originally had three legs. One of them turns out to be bad but the other two seem fundamental to large-scale systems programming.
> I'd also like to know why you think REST is disturbingly anti-OOP.
So, this is pretty abstract, but REST philosophy seems to put a lot of emphasis on representations of data while discouraging communication of operations on the data. The way to operate on the data is to GET the representation, modify it, and then PUT it back -- not to POST a message saying "please perform operation F". I've heard several hardcore REST advocates specifically argue against communicating operations, deriding it as "RPC" (which I guess they see as derogatory?).
In practice I think most people only follow this philosophy when it makes a reasonable amount of sense and ignore it when it doesn't.
At a very high level, they can guide the conversation well. As soon as the boots hit the ground, though, the lines between subsections of a design are ridiculously arbitrary and not at all disconnected from each other in a meaningful way.
In physical systems, this can be blurred when you have more obviously consumable parts of an object. Think the brake pads of a bike. That said, the wheel of many bikes is also a consumable part of the braking system. Such that the question is less "where is the separation of braking from drive train" and more "what parts are easily removable from the bike?" And it is definitely the case that that question is not necessarily built up in a taxonomy, Though it is certainly usably described in a variety of them.
One could easily change the taxonomy from "wheel" and "brake pad" to "spinning surface" and "stopping surface." And if you want to construct a bicycle by composition, you know that in order to implement brake() you need two items which implement SpinningSurface and StoppingSurface. The SpinningSurface could easily be of type WheelRim, but it could also be of type BrakeDisc. And in both cases the StoppingSurface is of type BrakePad with the difference in how BrakePad is applied to SpinningSurface.
And with this taxonomy you could also allow users to use Shoes, WaterBottles, Wrenches, and a whole host of other objects as StoppingSurfaces.
So the problem isn't with trying to encode anything with types, the problem is that people try to encode everything with types. You have to pick your battles, and you have to devise your taxonomy such that you can make meaningful abstractions. That remains an art form.
I tried to make it clear that I do find high level taxonomies nice. Just the further you zoom in, the less useful a strict adherence to that will be.
What the heck is "top-down programming"? I've been writing code for close to 20 years and this is the first time I've encountered this term.
Google reveals results like this one [0], that explain it as "essentially the breaking down of a system to gain insight into the sub-systems that make it up."
So, in other words, literally any kind of programming? All programming paradigms are about building abstractions to avoid repeatedly dealing with low-level details.
Is this really a thing that people talk about?
[0] https://dzone.com/articles/how-does-top-down-programming-wor...
People hate bloat, complicated ways of doing simple things, and AbstractFactoryAdapterFactoryAdapterFactories. These abominations got built in Java, an OO language.
Toss out all the religion and all you have are a few useful programming ideas. I was around when a popular "software development tool" of the day was a template. (No, not what you are thinking. I mean, a piece of plastic with shapes cut out of it.) You could use to draw flowcharts. Then, "structured programming" came along, basically the OO of its day. Structured programming meant no GOTOs. Instead, use the things that GOTOs usually implement: while loops, if/then, if/then/else. Keep functions to no more than "a page" (i.e., entirely visible on an old 24x80 screen).
Structured programming was uncontroversial. People recognized progress and adapted it. This happened when there were very, very few independent software companies, and there was a miniscule market of software developers. I can see though, how it could have gone wrong if there were companies intent on exploiting structured programming by turning it into a complicated fad requiring salesmen, consultants, trainers, and all the other profit-making paraphenalia.
Though I have written telecoms billing systems in Fortran (and some Pl1/G) and worked on Fortran simulations of Breeder reactors n my Time - so I guess I am a Real programmer Tm
Its about using the tools you have for the right job some times oop is right some times not
What people really hate is "enterprise OOP" and its related ecosystem.
It's like Agile. Agile is fine. But people hate "enterprise Agile" and the whole ecosystem around it.
If the only reason is because the language doesn't permit otherwise (like Java) then it's essentially nothing but extra boilerplate. When you write a lot of code, this all adds up. Inevitably, over time, folk will grumble. There are simpler ways to express such things in other languages.
Sometimes less is more.
This is also part of why Kotlin so easily sells itself, despite not being perfect, it removes a lot of what you refer to as boilerplate (I prefer the ceremony of Java / some OOP-only languages).
Plus the multi-paradigm nature means there’s less consistency with 3rd party libraries, and they’re usually OO in structure, which obviously annoys me to no end.
When it’s omnipresent, it makes reasoning about code so much easier for me.
I mean sure, this is mostly true of Go more than most other languages though. I didn't mean to pick on Java alone, C# which is a language I love has this similar problem, though they seem to be heading slowly towards a multi-paradigm language, though the emphasis on OOP is still there, it doesn't feel as restrictive as Java.
Heck, even Kotlin seems to be getting rid of a lot of what I call the ceremony of Java whilst still producing equivalent bytecode (I can only imagine most of it is just syntatic sugar).
Which OOP are we talking about?
That ideas's been nagging at me lately. In his paper, The Early History of Smalltalk, Alan Kay wrote, "doing encapsulation right is a commitment not just to abstraction of state, but to eliminate state oriented metaphors from programming."
Basically all code I've ever seen in OOP languages is incredibly stateful. And it makes a lot of reuse difficult, because that stateful behavior makes it difficult to confidently compose things from smaller pieces. That stateful behavior means that the class's internals aren't truly encapsulated. You still need to understand those details about how its state changes to interact with it successfully, even if you can't observe the state directly.
That syndrome can lead to over-abstraction and tight coupling of objects.
Java offers only one mechanism that gets abused for everything. Contrast with C++ (or Common Lisp, Haskell, Python) that support numerous abstractions, so that the best may be chosen.
So, people don't really hate OOP. They hate people who learned to program on Java, and still think that way.
It's difficult until you also consider conlangs like Kēlen[2] which also attempt to build a kingdom of nouns. OOP is a hammer that makes everything a nail for the same reason Kēlen is a conlang rather than a humlang. There is a certain element of hoop-jumping that takes place when a language doesn't have the grammar to express things naturally.
[1] https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...
However, if you turn your back on OOP entirely, where does that leave you? You have to go down the functional programming route.
FP has it's own problems. Ever see a large FP-style JS program? It resembles procedural code. Functions strewn around the codebase with no hierarchy or compartmentalization. Anything can (and will) call anything. You pipe data along from function to function, but you quickly find that the chunks of data that you're sending keep... growing... because you can't keep the entire codebase in your head, and you can't remember that variable X is equivalent to Y in all cases, so you just pass Y along also.
It is this superficial understanding of OOP that led to a somewhat bad reputation. At least in the form "getX" and "setX" it would be more of a data structure with mostly useless methods around the fields. If a programmer is able to set each and every field from the outside through a setter, there is no encapsulation at all. To have objects that deserve this name, a method should be more like a "transaction" on the object, so related fields should be changed in one shot.
Apart from that, I don't think it is useful to think in separate camps (OOP vs FP) here, since both approaches are complementary in many cases. It is not like you have to decide for the one or another theses days, since most common languages support OOP and FP style.
I've always liked Smalltalk's philosophy on many things and a key feature of its idea of OO is dynamism and 'late binding of all things'. Popular OO languages like Ruby or Python borrow heavily from Smalltalk and they're great to use and very popular.
It's the combination of static typing and OO I think that leaves a bad taste when it comes to OOP, and I think that's also reflected in newer languages. A lot of younger statically typed languages like Rust or Go seem to have ditched the OO paradigm, and embraced FP.
public class Main {
public static void main(String[] args) {
System.out.println("This will be printed");
}
}
>java Main
Also, there is no way you could get things done, without spending a year studying what each of
Class, static, void, System, args, etc
meant.
It's hardly surprising there will be a number of people. who realise that that's neither the only nor the most efficient hammer to drive in all types of nails.-- Edsger Dijkstra (1930-2002)
Some people like flexibility, but most people just like having a detailed set of rules to follow.
I think this is the reason behind a lot of trends in our industry: GitFlow, Scrum, Kanban, TDD, even FP.
The ones that get complained about, and the ones that don't get used.
It also has classes with "Iterator" in the name that are not iterators. They're containers, over which you CAN iterate. You'll be pleased to hear, however, that the actual iterator is NOT named an "IteratorIterator". The name of the actual iterator class contains no clue that it is an iterator.
Inheritance is really just about writing better namespace and domain / contexts of your code.
Polymorphism is really only ever an "issue" I've seen when dealing with strong typed langs and all the langs where it has become an issue they provide interfaces to aid in Polymorphism.
I don't think anything about what was listed is #1 exclusive to OOP and 2 doest not already have best practices to address in OOP and other langs.
Everything that was written about was not about the problems they solved but the patterns used to address the problems. I think that makes it too subjective to be constructive.
I will say though there is nothing fun about mocking complex state in tests for a rails app let alone if that state is shared or easily mutated.
He was trying to answer why functional programming isn't mainstream but the opposite is what we're posing here and I think it's for many of the same reasons.
Network effects: a lot of these languages were mainstream earlier on and got buy in from a lot of folks who are invested in their continued adoption and success.
Platform exclusivity: some companies push their own language ecosystems on their platforms and those languages are often OOP/multi-paradigm languages.
Specifically to OOP we have to ask why is the particular style of C++/Java OOP the norm? Why didn't Eiffel, Smalltalk, or Self reach the same market?
[0] https://www.youtube.com/watch?v=QyJZzq0v7Z4
edit: forgot the link -- oops.
It ends with talking about how loved Scala is as a language, while most of the Scala code I've seen is more OOP than FP.
Rather, people need buzzwords do dress up their non-thoughts on software architecture, and no new ones supplanted OOP's so they are still in use.
Maybe someday "container" will replace "object" :/.
The premise of the question is false.
There are a few people who hate OOP, and a larger numbers who hate (or at least dislike) the conditions in the period of unquestioned OOP hegemony when that square peg had to be shoved in every available round hole.
"OOP is just a mental model. Deep down everything is made of bits. The church of OOP has failed but if something looks like a duck, walks like a duck and talks like a duck it probably is useful to make a duck class. We're now down to fighting for nuances. You can do most things with OOP or without OOP but each path has some upsides and downsides and most of the time it's good to use some things it provides where it makes sense and not get too religious about it. The great architect has the foresight on how the code will be used in five years and design it accordingly."
There is something universal in our understanding of temporal logic that makes objects changing over time an intuitive model for a computer's internal state.
But that is exactly what a computer does: changes state over time. Languages ignoring that fact are prisoners of their own niche and will never be as popular as those which don't ignore the hardware.
But I have never really understood the desire to move to pure functional programming for the following reason:
I always end up with a group of functions that are repeatedly passing around the same variables. It might only be two or three or four variables, but quite often it seems that I can make the code less noisy by assuming that the state is attached to the instance already and so it doesn't constantly need to get passed around.
And I know someone is going to say I am just not factoring things properly, but after a number of years of repeating that experience, I don't buy that anymore.
The premise of the question "If everyone hates it..." is invalid.
Wait, what?
Maybe not everyone hates it.
Like a lot of things, it's a matter of culture in academics or elsewhere.
This article doesn't talk about the drawbacks of OOP, one is the tendency of programmers to make things abstract when it's unnecessary. And it doesn't even try to explain why people hate it.
In france we call an regular subject point a "maronnier", which is tree that gives fruits every year, fruits everybody likes.
Say you have a string, in Java, Python or whatever. It it more natural to uppercase it in an oop style mystring.upper() or a functional style upper(mystring)? Personally I think it makes things more manageable to keep the data and functionality together.
I know almost nothing about "OOP', but I was under the impression this was bad OOP?
In DB oriented programming, instead of god objects that store global state for everything under the sun, there is a god database that stores global state for everything under the sun.
Historically, software engineering has been a cat and mouse game of referencing global state, finding some clever way of hiding it, and then finding an even more clever way of referencing it again.
Lather - rinse - repeat!
For more info, see the docs for the Haskell extension: https://wiki.haskell.org/Existential_type
Also, it isn’t like OOP is one programming paradigm. It is several that make up one region of a landscape. And within each paradigm there is room for styles.
Finally, if X is better: then ask instead how you (yes, you) will make other people use, and like, X. People change languages because they find something that helps them achieve goals.
Also: “what has become our past, forever remains so”
— Dijkstra, on trendy 90s/millennium OOP in academia.
1. Heavy use of reference semantics prevents local reasoning about code. 2. Inheritance is really brittle. A subclass can break superclass invariants.
I don't think anyone really contests either of those... it's just a matter of how much you can put up with.
This is a good one if you extend it a bit further: they are concerned with the proper taxonomy and it usually happens to be wrong.
Because some problems still nails?
Having state in a closure does not imply that it's OOP, so I'd really like to see those frameworks for reference.
- still the main target in interviews (GoF pattern much) ?
I am using FP together with OOP when it makes sense, with great results. Both approaches have their advantages in specific contexts.
Abstraction and Encapsulation? Every programming language lets you do that with functions (or some other way).
Inheritance? Even OOP die-hards favor interfaces over inheritance.
Polymorphism? Interfaces (like in Go).
What's left to distinguish OOP with? The bad choices that Java made, like not allowing standalone functions i.e. Execution in the Kingdom of Nouns. That's what everyone hates on when they say they hate OOP, they hate Java's style of OOP.
When you look at the 'virtues' of OOP it all falls apart because other non-OOP language achieve the same thing without being Java.
>Significant object-oriented languages include Java, C++, C#, Python, R, PHP, Visual Basic.NET, JavaScript, Ruby, Perl, Object Pascal, Objective-C, Dart, Swift, Scala, Kotlin, Common Lisp, MATLAB, and Smalltalk.
So pretty much every popular language out there. Doesn't look so hated. :)
Cake should inherit Food or Pastry or something and Eat should be made a generic that accepts a base class
/sarcasm
1. A → B
2. ¬B
∴ ¬AHere are some things that OOP does not give you:
* Is your state normalized, i.e. saved only in one place? You do not know.
* Is your execution predictable? Parent to child? And how do you do it when it's reversed.
* What should be public and modifiable and what should be intermediate state internal to the object. And should intermediate computed values be stored next to values determining the true state of your application? (even though there are "public" and "private" modifiers it is quiet difficult to determine what is truly public and cannot be modified through some method. This makes it more difficult to understand what the state of your application truly is and what are intermediate convenience values).
* Are there limitations on passing state around? Or can large chunks of state be passed to multiple actors in the form of classes who change it concurrently or might store it for later use?
* How much state should be in a single class? In other words: how many classes can a class encapsulate and how complex could those classes be?
* Should something that has no state be an object or just a function (in some languages you are not given the option at true, but can't say that it's awesome).
* Should you be allowed to use event buses and how to prevent spaghettification? (cause events really are akin modern goto statements).
* etc.
Basically the ability to compose the state, type and methods is so wildly open-ended that almost always what I see is just an ever expanding convoluted bloat of state spread ts chaotically that it become extremely difficult to see what the state of your application is at any moment. Everybody has their own idea of what an object _is_, what should be an object and how they should come together. And that is for every developer on the team. So you can follow the absolute best of OOP practices and still end up with a mess.
Let's look at a counter-example. It is not a system that claims to be universally usable but that is the point. I'll use the, quiet banal, example of React. Yes, it is a framework, and in a way it does have objects, but really it borrows heavily from functional programming in the sense that it says that state is king and the single most important thing in your program. That you _must_ have strong constraints as to how your state is created, organized and modified. (In contrast to a class, which is state that you can pass around as much as you want and the whole codebase could modify it at will).
* The application is always a predictable tree of dependencies.
* State is either stored separately altogether (using a state-store such as redux, mobx, etc.) or bound to a particular element in the tree hierarchy.
* State only flows down the tree, ideally immutably.
* A component (unlike a class) cannot modify state of anything outside of itself.
* Only "has-a" relationships are allowed - no polymorphism.
* Only event can be passed up.
* State is essentially a collection of easily inspectable primitives. Meaning that even though a component can _contain_ another component a class is not a valid member of component state.
While a system like this could be built using OOP primitives (classes). Nothing about OOP suggests this system. Yes, you can build it, but can build something absolutely unrelated. And, really, it enforces things that are the antithesis of many things that "proper OOP" espouses.
Perhaps the main thing of any system is to create a predictable and well-structured relationship between elements of state. I think that wonderful examples of the basis of such a system are Go and Rust. To be honest, I barely know the languages, but from what I see is that they make _type_ the most central element of the language. If you have your types well-defined and be separate from how the types are used you are already suggesting many things that could prevent state-sprawl. A struct defines the basic primitive around which your program is based.
* Struct has no private intermediately computed values. If you do not want to recompute something - memoize, do not bloat the state with random data that is inseparable from you base state.
* Structs provide a very predictable "has-a" top-down dependency between state
* No inheritance, so composition is enforced.
* Keeping types and functions that operate on those types makes it very explicit what a function does - is it a method or is it a function?
Scala was invented to merge OOP and FP.
The article is nonsense. It offers no alternative and then wonders why the thing everyone uses is still popular.
In practice most OOP code will violate some functional stuff.
There is no reason you can't do this in C# or Java.