I'm pretty sure a lot of the popularity of oop actually came from modularity which came with it (although I do find inheritance useful, just not multiple inheritance).
I think C would be 10x more useful language if it ditched include files and had "import" like python
No, the biggest benefit is first-class modules. This is a feature that for example FP languages have struggled to incorporate. "Encapsulation" with OOP is a bit of a misnomer, as state is not encapsulated, it is just hidden. Such hidden state then interferes with composition. With true state encapsulation, a function can use mutation on the inside, but appear pure on the outside.
I'm interested in this, since I thought the concept of encapsulation is maintaining, and hiding, state inside of an object. What's the difference between mutating state on the inside and hiding state on the inside? Aren't they practically the same?
> What's the difference between mutating state on the inside and hiding state on the inside? Aren't they practically the same?
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.
I don't understand your reasoning. What does prevent you from having hidden state within a first-class module? It's simply encapsulation at module level.
Objects don't have to have mutable state. And even if they do, it can be unobservable. This is true "state encapsulation" but the OOP definition of encapsulation does not require this. Such objects are still very useful as "first-class modules", i.e. modules that can be passed around and switched at runtime.
On the subject of modularity, one aspect of OOP that bothers me is the conflation of namespacing (for organizing code that operates primarily on one object) with encapsulating the method as a class/object member. One could do the former without doing the latter! Basically a module containing structs and functions which act on it. In cases where the method(s) act on multiple objects, there might be other places/modules for them to naturally live.
What does that mean? Import for C doesn't make sense to me because 1) C code is never executed during compilation, and 2) C doesn't even support bare code, everything has to exist inside a function. So what would be the desired behavior from import in C?
* 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
I can take a stab at it. I think what trips many new C people is the fact that include is just what it says on the tin, include. You can put anything in there and it is subject to the pre-processor. Many books/faqs/stackoverflow/videos however implicitly tie library include and headers together. So the idea that include means include this library comes somewhat from that. It tripped me up some 27+ years ago when I started on C. The actual include a library comes from a different part of the system, the linker. The includes just handily have the bits needed to have the external references to it so the linker knows what to do. You do not need them in the include to do that. Since they were including those they usually have predefined bits in there.
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 think C would be 10x more useful language if it ditched include files and had "import" like python
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.
That's an apples to oranges comparison. Python is interpreted, and can guess what the programmer wants much better than a compiler that has to output actual machine code.
> 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.
Because you can do functional programming in OOP. In the sense that it was originally intended, which is just writing pure functions to avoid side effects.
Kind of disagree - in theory you can, but everyone in your org needs to do so. You can't be a "a bit functional". And also depending on external libraries might be an issue.
In practice most OOP code will violate some functional stuff.
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.
Riffing on this a bit. Consider two things. Firstly, that OOP is "the kingdom of nouns"[1]. Secondly, consider mapping the human language space to the programming language space.
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.
I think multi-paradigm is better. I hate that in Java I have to have classes to do most things. Whereas in C I can just have a function. I wish more languages approached things like D or Python. Let the developer figure out what they actually need and fully empower them.
It's not about having a name in front of the static method, it's about e.g. say I have IO readers, and I want to provide a method to drain a reader, or copy a reader to a writer, why should such methods have to be class members?
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.
> 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.
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).
The problem for me is that multi-paradigm languages, out of necessity, don’t have the feature that I value most about FP languages: immutability.
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.
Java is trying to force a code architecture on the devs. It's useful in enterprise situations where devs cycle in and out but the code base has to stay. You never have to guess where or how something is defined. C/C++ on the other hand gives you 1000 ways to shoot the person who replaces you in the foot.
Exactly. This is what people bashing Java just don’t seem to understand. Training a new Java employee on an existing code base is far cheaper than on a C/C++ codebase. Speaking from personal experience.
I've found it just as simple to teach someone a Python codebase, and I do have OOP in most of my python projects, but sometimes I don't and that's okay, because not everything needs to be an instance of a class. D is not as prevalent but I'm certain showing someone around a D code base wouldn't be too complicated.
> You never have to guess where or how something is defined.
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).
I have a codebase in which the naming convention somehow went so wrong that many classes with "Factory" in the name were made in some kind of Factory class, but are not themselves factories :/
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.
Exactly, while FP is a good addition in some cases, where is the "everyone" that hates OOP? OOP is still 1000x more widely used and loved than FP to say the least.
Part of the problem is that “OOP” is poorly defined and lends itself to no-true-Scotsman arguments. If you criticize OOP for its characteristic use of inheritance, people retreat to “true OOP is about message passing” or “true OOP is about encapsulation” and “inheritance has nothing to do with OOP”. Feel free to replace “inheritance” with any criticism of OOP, such as the gratuitous use of mutability relative to other paradigms or the remarkable popularity of gratuitously large object graphs (the “banana with a reference to the gorilla holding it with a reference to the entire jungle” problem). Everyone insists their own definition of OOP is the one true definition, but none of these purely theoretical definitions absent any of the faults typically attributed to OOP actually exist in the real world (or perhaps they do exist and we just call them “data oriented programming” and “functional programming”).
For a parallel in a totally different domain, see Umberto Eco's definition of fascism [0]. It is a list of 14 properties typical of fascism, but it is not necessary to actually possess them all to be considered fascist (and they're mutually contradictory).
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?
I find it's more helpful to think about these labels as regions in a continuous multidimensional space. One such dimension is im/mutability, and you have languages like Haskell that exist on an extreme of that continuum and then languages like Java which exist nearer an opposite extreme in which mutability is common, and languages like CL where mutability is permitted but relatively infrequent. Similarly, another dimension is the frequency of inheritance, another is the aggregate cleanness or spaghetti-ness of object graphs (I'm sure there's a mathematical term for this), and so on. CL permits some mutability, but it pretty clearly belongs to the cluster that we typically call FP, and while definitions for FP do label slightly different but largely overlapping regions in this multidimensional space, there is relatively little variance.
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.
I would consider myself to practice "data oriented design" rather than "object oriented design" but I still usually encapsulate things in objects after I've figure out the data flows. OOP is really a collection of ideas, some terrible, some great.
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.
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'.
<- a loose associate of this "everyone" here. (10+ years exp, proficient in FP and OOP languages.) Primarily Scala dev now so I write code that's both functional and object oriented. I'd say my code leans more towards the functional side of things, favoring composition, type inheritance over class inheritance, separation of data structures from behavior that acts on data, and a reduction of shared state to the bare minimum. However when it comes to persisting state throughout a programs lifecycle then objects/actors become a great way of handling that data in an easy to reason about structure. They're great for encapsulating data after all.
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.
I don't understand the perceived antagonism. I like FP, I like OOP. Most of my objects immutable, and "change" methods return a new instance of the object with the expected change. It's a great way to code.
Languages with immutability by default are the best. It's kinda like safe vs unsafe in Rust. You can do lots of things with immutability or safe Rust but when you need mutability or unsafe Rust then you just add the necessary keyword.
The "dangerous" parts of the application are now highly visible and neatly separated from the harmless parts.
I really wish Ceylon had taken off, it was (is?) a better "better Java" than Kotlin. Aside from having intersection and union types before typescript, everything was immutable by default... and you had to type out the full word `variable` if you wanted mutability.
This is very close to writing FP code in an OOP language.
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.
> If everyone hates it, why is OOP still so widely spread?
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.
C++ templates are structural / duck typing. Powerful stuff, though a simple core. Right now Rust has some strong hype as a C++ successor, but I also hear that it doesn't really support OOP per se as a traits-based language. And I'm just thinking -- but I like my classes.
I like Rust's solution. It doesn't have inheritance, which eliminates majority of OOP "architecture astronautics". It still has encapsulation and polymorphism, so you can do OOP, but the language nudges you to keep it simple.
ELI5: How do you have polymorphism without inheritance? That is, if I have a Foo with a virtual function f(), and I have Bar and Baz that inherit from Foo and have their own implementations of f(), then I have polymorphism.
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)?
>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)?
I see the other replies mention Rust/Haskell's traits and typeclasses; but a better example to use imo is Python's duck typing.
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.
Bar and Baz can be a Foo without necessarily being derived classes that inherit from base class Foo.
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 sense
> Agreed 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.
Also row polymorphism can solve most of the problems subtyping can without sacrificing type inference and without the complexity. Too bad it hasn't really caught on.
Mutability is one of the most overused constructs but this has nothing to do with OOP. The vast majority of variables and fields simply never mutate. That's why its overused. People use a feature simply because it's the default. Making variables immutable by default and requiring explicit mutability would do a lot of good in many programming languages.
the other thing is, alot of "state" is emergent or can be derived... lots of people seem to not think in that way and complicate things with too many variables
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
}
Another major issue that isn't called out almost ever is that, at least within the context of an object, member variables and methods are basically global, so you never know where the state is being modified. This is especially bad in large classes.
Meh, I feel that things like "single responsibility principle" are easy to say, but in practice (a) defining what you mean by "single responsibility" is a challenge, and (b) you're often trading off ease of handling things internally within a class for more difficulty in how you have to communicate with other classes (see microservices).
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.
> I feel like I can confidently say structural typing, in practice, is MUCH easier, better and fun to work with than nominal typing.
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.
There are other ways to encapsulate besides private member functions. And, honestly, private member encapsulation is overrated. I rarely see a design that would have been dramatically improved by language-enforced member accessibility restrictions. The problems are typically more basic than that, like complete disregard of the Single Responsibility Principle.
I like private sometimes, but encapsulation is at its core tied to the notion of identity and hence nominal typing. If a name stands to represent something rather than its structure, well, that name is basically encapsulating.
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.
For instance, yes. And some languages and idioms also allow objects to be provided without exposing their types at all. Declared but undefined types, for instance . And all languages would support opaque IDs as alternatives to references that expose irrelevant or harmful APIs.
I'm obviously not who you're asking, but I'm going to cheekily show up with my own thoughts on the subject, anyway:
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.
Thanks for the detailed explanation, but I realize that I was misreading the whole time and so you answered a question that I actually didn't mean to ask. Apologies.
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
TypeScript compiler error messages really are bad. The fact that structural type errors cascade so easily, though a lot of that could be alleviated with some clever compiler implementation.
Really? Can you give an example? I just have never, ever had this problem in a production TypeScript system, or at least I just never had a problem where I couldn't immediately find the root cause error and fix it.
> 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.
I'm developing a structurally typed language now. Is there some research you could recommend me?
> so you'll see "composition over inheritance" MUCH more than you'll see inheritance as the recommended way to do things.
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".
> I feel like I can confidently say structural typing, in practice, is MUCH easier, better and fun to work with than nominal typing.
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.
> , 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.
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.
It's what gets taught first in higher education. I first learned OOP, and I was neither here nor there about it. I didn't learn about FP until completing my degree program. FP clicked for me, my team uses it, everyone else in my company uses OOP. Nobody seems bothered we use FP, but also nobody's checking, either. It's not that I dislike OOP either, it's that FP feels like a much more natural fit.
OOP is really interesting to someone like myself that comes from a philosophy background. In a python framework, i find object to be very useful in conceptualizing my programs (i'm more able to hold the code in my head).
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.
I would argue that it’s very rarely useful to mutate like this at all, and this is one of my grievances with OOP in practice. In particular, I don’t need to mutate via methods—this kind of encapsulation is rarely useful; it’s not like one day I’m going to be able to swizzle the implementation for a network-backed implementation without introducing a breaking change (and a whole host of performance problems when iterating over a collection of objects).
In addition, there are things that just don't need to be separate classes, they should be static methods or constructor options of an existing class.
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.
> OOP is nice when it is used concisely and when objects correspond 1:1 to real things.
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.
Although I have only a very superficial understanding of modems, I'd say demux() should be a function library, not an instantiated object. But even if it is an object for various reasons such as caching, that's quite fine with me; a Demultiplexer can be thought of as an conceptual component that a Modem "has" inside it.
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.
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.
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.
A Python background explains that, as y'all tend to worry less about memory usage and re-use. This can have its upsides, but it probably creates a bit of a blind spot here, because it's referencing the sort of thing one probably wouldn't do in python.
That's because you're telling the compiler what the type of the variables is ahead of time so the compiler knows whether you're using the type correctly. As opposed to in Python constructing an object and adding functions to it one by one at run-time, and only checking that the type is used correctly when the type is used.
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.
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.
That a reasonably parody of a lot of Java code - but I don't think it's really a property of Java the language but some odd principles that seem to be popular (or used to be popular) in the Java community.
BTW You missed:
public static final String STRING_STRING_CONSTANT = "string";
Thank this is a very nice answer and it makes sense to me. Indeed I once sat down to try Kotlin for a day. The amount of code I had to write to get a json from an api into a dict that I could address by key was baffling to me. In python it took met 2 imported modules and then 2 lines (something like
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.
That works until you want to let the user rotate the phone without interrupting the stream. And, creating a new camera probably means freeing the resources from the old camera, which, for some cameras, can mean power cycling.
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.
I think OOP as a way of abstraction. It make sense to abstraction something in OOP style, for example, collection such as LinkedList, Stack.
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
I don't hate OOP. Sometimes an OO design is nicer than a non-OO equivalent, but the problem is that many people run with that and try to apply the pattern everywhere (especially if they're from a Java background where that is the norm). I think the reason is:
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 Turing machine implicitly includes time as a sequence, and the lambda calculus does not. It is easier for the amateur programmer to reason about time as a sequence. So the lay person begins programming easily writing imperative programs and struggling writing functional programs. The years turn the amateur programmer into the experienced one. Before they only understood imperative programming, now they understand functional too. But they still work with the amateur programmers who only understand imperative. They both use imperative in order to work together.
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.
"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.
OOP is great. Certain interpretations of OOP that certain popular languages adopted are bad, but the problem is not really OOP. The idea of objects is one of the most powerful ideas in programming, because it is able to bring a level of modularity that is otherwise very hard to accomplish. For instance popular very high level languages like Ruby and Python are so easy to use and to build things with, because you have such initial collection of objects of all the kinds that are so trivial to use and versatile. The Array object may have a lot of methods in Ruby, but such methods are self contained inside its implementation, and to have a lot of tools for such data structure is great. This idea, that we give for granted when using Ruby or Python now, is quite impressive.
Having a collection of methods associated to an object isn't really OOP though. You can have one without the other.
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.
Sort isn't a great example because its presumably only looking at public fields of the elements only and could be functional. However, Arrays in ruby grow so they presumably have private fields and manage their own internal state. This is what the parent was referring to.
You can do that with various constructs without doing OOP style though.
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)
OOP is not much more than single-dispatch runtime polymorphism.
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.
>Collections.sort(data) approach, not the polymorphic data.sort()
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.
Java like C++ or Swift uses single-dispatch polymorphism, so only the receiver (the argument before the '.') is used at runtime.
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'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).
I consider "true OOP" what SmallTalk introduced. And the most fundamental thing in SmallTalk OOP is the encapsulation of complexity in the idea of an object, and the ability to pass messages to such objects that can be other objects, including "blocks". There is more than that to OOP, but I think that the fundamental idea is that code and functions operating on a given software component are defined within the concept of a class, and that objects only communicate with the other parts via messages. From that, a lot of things happen that are not obvious given this simple idea of OOP. For instance every object knows how to turn itself into a string, or to compare it with another one of its kind or of different kinds. And so forth.
Maybe. But the article opens by quoting Dave Robson's article in the 1981 Byte issue and even links to the archived version of the magazine. That issue was entirely dedicated to articles about Smalltalk. Robson's article is also about Smalltalk.
While that's true historically, SmallTalk has more in common with some functional languages (like erlang) than it does with even Ruby, which is directly a SmallTalk derivate.
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.
> SmallTalk has more in common with some functional languages (like erlang) than it does with even Ruby, which is directly a SmallTalk derivate.
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.)
If you're saying that the idea of namespacing together a data structure and functions that operate on that datastructure is OOP then I have to disagree; plenty of other paradigms do that. Likewise the idea of polymorphism: plenty of paradigms can express an interface for "can turn into a string" (and I think the idea that every object needs to support this is a mistake). The part that's unique to OOP is encapsulating mutable state in an object, and that turns out to be a mistake, IME.
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?
Pretty sure it’s a cognitive bias. The Array.sort method is obviously in the Array module. While to find the sort method, I’d have to find out what class data is in.
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.
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.
I think this is a really important comment that deserves to be discussed.
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"?
>>>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.
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:
> 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?
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!).
Heh. I'm (I hope) a person with sage wisdom... on some subjects. On others, I'm a crank. And you, who call yourself a crank, I suspect there are some subjects on which you have sage wisdom.
My favourite HN stereotype is that every comment chain involves the next poster correcting the last poster over a tiny detail, and then descending into long threads arguing minute details of no consequence.
I think this forum attracts a non-trivial number of people who have a hyperfocus issue beginning with the letter A.
It's even worse when the thread veers into something political. You can write a 5 paragraph essay about some topic and someone will eventually come out of the woodwork to point out: "On paragraph 4, in this statement, you generalized and said 'all' and I can find a counterexample. Aha! You should have said 'many'! Your entire point is therefore invalid!!" Pedantic nitpicking is just part of this culture.
My favourite fallacy of all is the fallacy fallacy: whereby it is a fallacy of an argument to reject another argument if it contains or is composed of a fallacy. (https://en.wikipedia.org/wiki/Argument_from_fallacy)
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.
Right. I call it the Hacker News Right-Side Effect (or should that be Rule, like the Off Side one ... :):
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 for about 30 years. Started with C and BASIC back in the day, and lived through lots of fads. What has always been true is that there is high value for engineers who can solve problems and build systems, and there really hasn't been a watershed moment where some new programming paradigm and is sooo much better that everyone switches. What's important is to learn how to write the kind of code that solves the kinds of problems you need to solve. FP may be the right answer. OOP might be the right answer. Array programming might work for something else. The important part is to understand what a paradigm is good at and what it is not, and select the right one for whatever you are working on. Everything else is just religion.
"The important part is to understand what a paradigm is good at and what it is not, and select the right one for whatever you are working on."
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.
Started at ... 10 and have been doing this for > 35 years in some capacity.
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)...
That's understandable, you learn one tool - hammer at 20, you treat all problem as nail. Screws? Hammer it and lock with duct tape. Later on you learn another tool - screwdriver. Now you see screw, you use screwdriver to tighten it.
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.
> The problem is it takes years to learn one tool,
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.
> 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.
> 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...
Sometimes, the domain has already been chosen ("this application is written in Fortran++"), and your task is to find the best tool/technique given that constraint. This means you cannot necessarily bring your knowledge of Closedjure or Rusty to bear upon the problem even if you believe it would improve the situation.
Yes, that happens. One thing I've found that has been helpful is identifying the stuff about idiomatic "language X" that makes it "good", and try to adapt some of those traits to the current situation. I can say as I've moved in and out of other languages and companies/clients/teams, I've been able to identify good habits and behaviors, sometimes those are a result of languages or tooling in different ecosystems.
That helps for 'solo' stuff. Trying to introduce those ideas and techniques in existing teams is almost always more difficult than it should be.
I agree, but I tend to view it as proof that we don't know what we're doing yet and, to some degree or other, all our programming tools are still awful. The best you can do is look at your problem and your preferences and choose the combination that gives you the least amount of awful currently available.
That's pretty miserable, really. At least it pays reasonably well.
I perceive the soul of what you're saying to be: don't get too obsessed with how your program is made, and focus on how it actually _works_. You don't get extra credit for building your app in Rust. The value of building it in Rust must eventually cash out in how well the software behaves. Suppose we called this the "Whatever Gets It Done" principle.
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?
The only way _to_ experience is _through_ experience. You’ll probably get more out of just picking a language as your “primary” while dabbling in other things for context/inspiration/awareness.
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.
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.
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.
They're right, and they're wrong. OOP is considered an anti-pattern... by at least someone, somewhere. OOP is also considered The One Right Way, by someone, somewhere.
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.
Seriously. From what I've seen, OOP haters either write shit code in general or they're working on something where FP makes sense and they don't actually mention that when they shit on OOP.
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.
As someone who is more of an FP fan (though certainly not an OOP hater, just indifferent about OO)-- I would appreciate articles on what the tradeoffs of FP. It would better articulate what sorts of problems are conducive to one style vs another.
I am a mixed paradigm language person Python and previously Perl. I like OOP for higher level organization and functional for smaller details. I would find it difficult to know how to start a moderate sized project in a purely functional style.
FP isn't well defined so these sorts of debates sometimes turn into a no true scotsman fallacy. For example laziness is pretty much agreed now to be a bad idea even though it's at the core of Haskell, the flagbearer for FP languages.
There are some other more subtle issues.
This article goes in depth into some less well known ones related to data structure efficiency.
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 ...
>>OOP is fundamentally a paradigm about modelling a mutable world
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.
When I started programming FP meant Lisp/Scheme/Caml Light, Miranda was still being discussed, OCaml was just starting and was known as Objective Caml.
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.
Perhaps a controversial opinion: there are no bad programming languages, just bad practices. Learn both C++ and Rust. If you use them enough you will come to see the pain points with time, for yourself, and they will not be problems so much as opportunities for creative solutions. I have my opinions about languages but one constant is that I like programming and all of them facilitate this in different ways.
IMO anyone who takes issue with this level-headed maturity is... well, probably still of an age and in a career moment where the absence of experience is perceived to be best addressed through bravado and "passion."
I dunno. Is anything 'bad'? Maybe there's no difference between anything. I feel like we're talking about building materials and someone pipes up to say 'no building material is bad', which is kind of true, but then we all die because our bridge was built with glass. We do have to be discerning, and that does involve real criticism of language design and choice.
The differences between languages / frameworks is probably overstated. I like Python and Django and dislike JavaScript. I can still solve problems in both, but I can make it more cleanly and elegantly in the former. Maybe I just need to do more JavaScript, but there definitely appears to be more warts in JS than Python.
I'm speaking from personal experience: due to hiring freezes I had to deliver significantly more by myself for clients than expected, and the only way I was able to do it effectively solo was to use languages like Elm instead of JS, to guarantee I wasn't making certain time consuming errors that frankly weren't really acceptable to make given the constraints. It taught me, concretely, that there can indeed be massive differences between languages and paradigms, and that choice can make or break you in several different ways.
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. ;-)
No, there are bad programming languages. It's just that it's all of them. We have yet to develop a language which lets you tell the computer what you want it to do and have the computer do it in an efficient manner without having to write test suites (which have bugs) and do manual testing (which misses things) and static analysis (which is imperfect) and do performance testing (which is hard and expensive and may or may not be able to replicate the actual performance conditions your software will experience in the real world). But this is not to say that some languages are not worse than others, because they are.
Well, to be honest, we don't have any kind of language for that. It's as if these serializer-of-an-imagined-state things which we call languages are not good for communicating our intentions, or anything really. Godel or Wittgenstein for references here, probably.
This is the reason I think game reviews on Steam are excellent. Next to every review you can see how many hours the user has in the game. A negative review is read differently depending on whether the user has half an hour or 4,000 hours.
> But then I start to wonder.. is this actually elite minds speaking ... Or, is it just some overly opinionated junior person
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.
It is readily the case that this small but noisily opinionated group occupy much of the signal bandwidth available in their respective communities, and thereby become convinced that they represent the prevailing opinion of that community.
When in truth they’ve simply formed an echo chamber for their own loudly-stated views.
> 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.
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 think there's good evidence that this isn't just a run of the mill "grass is greener" grousing by experts.
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.
(going with the definition that frameworks are "your code gets called into by the community code" where libraries are "you call into the community code", please correct me if I'm wrong)
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).
Can anyone explain to me what the difference between a framework and a library is? I've looked it up several times but it always seems cloudy at best, and it seems like some things that are referred to as frameworks are pretty similar to some other things referred to as libraries.
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...
And what about React? If you search for "is React a framework" you'll come up with many, many people saying that it's a library. And yet, when writing a React app you absolutely have to structure your code a certain way.
1. Framework owns your main() function. Using libraries you don't lose control of your main().
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.
1. None of the programming languages I use regularly have the concept of a main() function (not dismissing the point, just saying it's language specific).
2. Yeah, heard this one many times. It seems very hand wavy to me.
3. That's more a cost/benefit analysis than an explanation of the difference.
> 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.
The hollywood principle is actually to me the definitive criteria. You plug your code into React and React ends up orchestrating and coordinating between your code, thus it is in charge of calling it and in what order etc.
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.
There's probably a sampling bias, because of the attitude that one should learn Haskell "to be a better programmer". The FP universe is far broader than the pure FPs. Need I remind: Twitter went from RoR to Scala, ostensibly for practical (performance) purposes, though with hindsight they could have figured out how to scale RoR. So it's hard to argue that nobody has gone the other way strictly for "practical" reasons.
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".
> So it's hard to argue that nobody has gone the other way strictly for "practical" reasons.
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
> If you want a really good (non-individual) example, take React
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 think it's lazy to dismiss the OOP as hate from experts who are complaining
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.
Yup, and what happens when you take one entity that has qualities you dislike and generalize it to all entities of the same color?
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.
> 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.
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.
It is hard to trace back the timeline history of all these things, especially as programming languages and their associated paradigms didn't evolve in a vacuum.
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.
That's my point. We should discuss the specifics and stop generalizing.
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.
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.
Subclass polymorphism in typical OO languages (Java, C++, Python) is nothing like type class polymorphism. Class inheritance is nothing like type class sub/superclassing.
It's quite possible for one to be good and the other to be bad (and I would claim that is indeed the case).
And then there is prototype polymorphism (SELF, JavaScript), pattern based polymorphism (BETA), component based polymorphism (COM, SOM, UWP), protocol based polymorphism (Objective-C, Swift, Smalltalk), ....
I'm talking about ad-hoc polymorphism, for example, in Java it would be interfaces and interface inheritance.
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.
If you define "OO" to mean "has any form of ad hoc polymorphism" then I agree that Haskell is an OO language. On the other hand, I suspect most of the profession would assume that the polymorphism requirements in the definition of OO extend only so far as what Java, C++ and Python provide, that is, subclass-based polymorphism implemented using a vtable reference per object (or equivalent). The Haskell community certainly does not consider type classes to be a feature that provides "OO".
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.
This confusion of "what is meant by OO" is what my original comment was about.
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.
Depending on your definition of the term, OOP can imply parametric & ad hoc polymorphism. Sure. But that in no way implies that all languages with that kind of polymorphism are OOP. That's gotta be, like, the first logical fallacy one learns about...
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 (?)
What would you expect to be different in a world where OOP really did suck?
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.
If it really sucked then people wouldn't use it. It's very easy to spot a universally bad tool. There are these screwdrivers with an integrated lamp to check if a wire is live or not. Only incompetent electricians use single phase testers. Amateurs buy them because they don't want to spend $30+ on a real two phase tester and that's fine because they are going to use them 10 times in the first week and then never again.
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.
> It's very easy to spot a universally bad tool. There are these screwdrivers with an integrated lamp to check if a wire is live or not. Only incompetent electricians use single phase testers. Amateurs buy them because they don't want to spend $30+ on a real two phase tester and that's fine because they are going to use them 10 times in the first week and then never again.
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".
The content of what you discuss is true there are juniors that argue for it based off of ideology. But the origins of the hate against OOP is not a cultural misunderstanding. I hate OOP and I'm pretty good at both FP and OOP.
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.
> I hate OOP and I'm pretty good at both FP and OOP. Well hate is too strong of a word. I don't hate OOP, but i definitely think it's a mistaken paradigm.
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.
>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...
Let me reverse the question; what metric would make sense to a new programmer? Experience dictates that the only measure which new people really care about is familiarity (see the debate between s-expressions vs. infix operators) and it's also the only one they feel strongest about.
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?
I doubt it. People aren't as dumb as you describe. Nobody is going to quote me without understanding or agreeing with me.
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.
> It's bad because OOP abstractions do not add any real benefits
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).
>Conceptually, procedural languages like C are doing that too for files, they’re just writing object.Verb as object_verb(Handle instance).
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 Armstrong
Many 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.
> The main problem with OOP is the promotion of mute-able state
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”.
>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.
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:
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.
>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.
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 y...
> Basically the DOM update loop that's popularly associated with languages like elm and frameworks like react
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.
> 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#:
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.
>Have very little idea, I don’t do web development.
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.
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 functio...
> The pattern is called Functional Reactive Programming.
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.
>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.
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 y...
> Meanwhile there are still millions of projects done with OOP, including super-complex AAA games
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.
> 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.
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.
>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.
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:
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.
>Well, your answers regarding OO calculus appear to be otherwise.
I'm obviously not trolling. You literally admitted you are. I can't flag you, so I'm going to plant a word here that will hopefully attract attention and get some admin to stop you. Stop trolling or fuck off.
You were the one stating that only FP has a perfect calculus representation, and have dismissed all the posts from everyone that that proved otherwise.
I asked for the papers describing specific semantic models of two mainstream FP languages to give you the opportunity to prove your point, instead you threat to flag me, I think Dan will take his own conclusions.
>You were the one stating that only FP has a perfect calculus representation, and have dismissed all the posts from everyone that that proved otherwise.
That's my opinion. That's not called trolling. Disagreeing with people respectfully is not called trolling. Saying the shit you said above: that you're out of food to continue trolling me IS trolling.
I'd flag you already but you need a certain rep to do that. I'm quite new.. but let's see if Dang actually does his job.
Cardelli is a highly reputed academic. That book is a pretty seminal book, lots of papers refer to it.
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.
> 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
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.
OK let's look at reality here. Anything on this earth can be formalized into a mathematical theory. That doesn't mean every formalization is accepted and accredited by academia.
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.
Cardelli's work is actually quite notable in the academic field of programming languages. And it's far from "random" or "arbitrary" given its constraints. It's a rather elegant exposition of OOP semantics.
Let me come at it from another angle. I'm a bit too blunt. I'm not saying people discredit his work. It could be beautiful and elegant. I'm saying his work isn't accepted in the mainstream meaning generally people don't know about it, nor do people study it and contribute to the theory.
Why are OOP and FP (Functional Programming) discussed as the only two alternatives, in this thread? Another comment a bit earlier than this one also seemingly made this assumption.
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.
It's weird because OOP and Procedural kind of go hand in hand. If you program in Java likely you're doing both procedural and OOP.
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 *).
Languages like C# and Rust are called "multi-paradigm", but when thinking about them and working with them I don't tend to think in terms of paradigms at all, just a big design space of possible features and decisions.
You really hit the nail on the head here. It’s so easy for people to see something that took years to create, and pick out the one or two flaws in it. Meanwhile, those flaws are exponentially small compared to the benefit that an idea might have had.
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.
Objects as encapsulation are fine. The author's claim that modules do the same thing is incorrect. Modules don't, in most languages, have instances. You get one set of data. Bundling data with functions is way better than global data.
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.
> 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 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).
> You get one set of data. Bundling data with functions is way better than global data.
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 don't, in most languages, have instances. You get one set of data. Bundling data with functions is way better than global data.
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).
Avid Haskeller here. It really depends on the domain. I find OOP to be a really good abstractions for building GUIs, a domain that FP falls short, FRP nonwithstanding. I've been using Swift to build mobile apps on iOS and I find its OOP abstraction a joy. So there's a data point there.
I would really like to hear your opinion about the problems of FRP (for GUIs, in case there are other popular uses). I use C++ and QML, a "declarative" (it's a somewhat fuzzy concept) language, for GUI programming daily. I have a bunch of techniques in C++ to tame mutable state, including use of a quasi-functional style where possible. More... "functionality" seems like a good idea in general, hence my curiosity what doesn't work.
A vocal minority (of which I am part of!) hates it.
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.
It's a stretch to think that the main reason that people don't use FP languages is ignorance or an inability to affect change.
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.
Why is it a stretch? Only a tiny itsy bitty minority even know what FP is (a little more these days because of some JS frameworks and reactive programming, but still).
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.
Because it's taught because it's a good way to compartmentalize code when you distribute the task of writing code. Writing code is in teams.
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.
I think it's trendy now, in general but especially in tech, to generalize anything that anyone doesn't like as "everyone hates this". It's not a new tendency, but it feels like hyperbolic projection became the new normal in the last few years.
In general, I've noticed a trend in most communities I participate in that criticism is inherently negative. There's no view that you can be critical just understand something or see how to improve it, if you're critical you're attacking.
490 comments
[ 3.1 ms ] story [ 325 ms ] threadI 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")
In practice most OOP code will violate some functional stuff.
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...
[2] http://www.terjemar.net/kelen/kelen.php
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.
https://tour.dlang.org/tour/en/gems/functional-programming
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).
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.
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.
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.
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.
https://www.youtube.com/watch?v=8GWZE2Y2O9E
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.
https://doc.rust-lang.org/book/ch17-02-trait-objects.html
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:
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:
whereas if you have any sense of when "now" is its much better to just calculate the age based on current dateI 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:
And don't do this: Do this: 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.
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):
This would be NICE use of OOP: OR This is painful use of OOP: This would be nice use of OOP: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.
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.
BTW You missed:
You're probably going to need to check `value_I_want` though. So maybe the checks aren't quite as optional as they look.
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.
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
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.
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.
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.
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.
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
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".
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.
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 ...
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).
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".
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:
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.
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? 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)
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:
is more reusable than addOne: “… 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:
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 y...
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.
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:
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 y...
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...
Here is a nice exercise, an FP calculus notation about the semantics of type classes in Haskell.
Afterwards, it would be nice to see the lambda calculus for functors in OCaml.
I'm obviously not trolling. You literally admitted you are. I can't flag you, so I'm going to plant a word here that will hopefully attract attention and get some admin to stop you. Stop trolling or fuck off.
I asked for the papers describing specific semantic models of two mainstream FP languages to give you the opportunity to prove your point, instead you threat to flag me, I think Dan will take his own conclusions.
That's my opinion. That's not called trolling. Disagreeing with people respectfully is not called trolling. Saying the shit you said above: that you're out of food to continue trolling me IS trolling.
I'd flag you already but you need a certain rep to do that. I'm quite new.. but let's see if Dang actually does his job.
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
vs. 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.
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.
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.
/sarcasm
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.