OOP is dead



  • @DogsB said in OOP is dead:

    dogsb Fucking junior devs. :belt_onion:

    With a :belt_onion:, that could get you thrown in jail!


  • Banned

    @Mason_Wheeler said in OOP is dead:

    Basically - my point is that popularity and quality are uncorrelated. Inheritance-style OOP isn't popular because it's good; it's popular because of Java.

    And how did Java get popular?

    It's the best 1995 had to offer. And then everything started depending on it. I'm pretty sure the latter had a much larger effect than the former.


  • Banned

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler okay maybe not leading. But certainly in top 3. But if you find even that questionable (it's incredibly hard to find any source of numbers for any server side technology), let's go back a few years and remember the times where PHP was the unchallenged king of everything web. Despite being incredibly crap and hated by pretty much everyone. Despite not even having any OOP concepts.

    Which version of PHP are you referring to?

    The one that was gaining popularity. As opposed to the one that was only losing more and more users each day.



  • I only created this thread instead of posting in WTF Bites because I recently complained about the lack of new threads in the side bar and didn't want to be a hypocrite. But it's definitely more fun to watch a new thread get derailed than to watch old arguments in an old thread. We should do this more often.

    Since the pointless arguments have already started, I might as well ask:

    @Mason_Wheeler said in OOP is dead:

    What exactly does heavy use of a specific feature of a language that does an abysmal job of implementing OOP concepts in the first place have to do with the principle of OOP being "dead"?

    How exactly does C++ implement OOP badly, in your opinion?



  • @dfdub said in OOP is dead:

    @Zenith said in OOP is dead:

    Not when you have 70 service classes that are all reimplementing the same dozen utility functions every single time.

    But code re-use is exactly the wrong reason to use inheritance and the easiest way to break the LSP. What you need are separate utility classes.

    And then you overspecialize your utility classes. The example I gave had all of the services being controlled by a service manager and having them all inherit from a base class with some common code would've precluded having to redo a ton of stupid work that a utility class couldn't have done anything about. The appeal to LSP, and LSP alone, is sort of a cop out. Almost nobody in practicality really takes advantage of it. Your average CRUD software is designed to work with one database and only one database. Your code standards style guides usually dictate what limited set of data structures you're allowed to use (subject to LCD thinking). And, ultimately, if you do have that utility class, that defacto rules out alternate implementations of practically anything because somebody will always shriek that it's reinventing the wheel.



  • @dfdub said in OOP is dead:

    How exactly does C++ implement OOP badly, in your opinion?

    Isn't "implements X badly" one of the defining features of C++? :tro-pop:



  • @HardwareGeek said in OOP is dead:

    Isn't "implements X badly" one of the defining features of C++?

    You're thinking of Raku. C++ invents a whole new language feature providing 15 different ways to do X, except the one straightforward way 99% of the users would want to use. If you want a simple interface, you'll have to write it yourself.



  • @dfdub said in OOP is dead:

    You're thinking of Raku.

    I can't be; I'm not suicidal.



  • @dfdub said in OOP is dead:

    I only created this thread instead of posting in WTF Bites because I recently complained about the lack of new threads in the side bar and didn't want to be a hypocrite. But it's definitely more fun to watch a new thread get derailed than to watch old arguments in an old thread. We should do this more often.

    Since the pointless arguments have already started, I might as well ask:

    @Mason_Wheeler said in OOP is dead:

    What exactly does heavy use of a specific feature of a language that does an abysmal job of implementing OOP concepts in the first place have to do with the principle of OOP being "dead"?

    How exactly does C++ implement OOP badly, in your opinion?

    Basically "in every way." Just off the top of my head:

    • The core sine qua non principle of OOP is inheritance and Liskov substitution. C++ makes objects value types rather than reference types, in open defiance of every OO language before and since, which breaks Liskov substitution 6 ways from Sunday due to slicing problems.
    • One of the key advantages of OOP is modularity and reuse. But C++ inherits C's abysmal #include system, providing terrible modularity at the source file level, and just to make things worse, provides no standard ABI, meaning there's no modularity and reuse at the DLL level as well.
    • Generics are an important part of modern programming. C++'s templates are perhaps the worst possible way of implementing generics: a separate language within the main programming language, describing programs that must be evaluated to completion for compilation to progress, that happens to be Turing-complete by accident so you can't actually prove that that will ever happen!


  • @Mason_Wheeler
    I wouldn't count generics as an integral part of OOP, but they're definitely useful (especially when the language supports covariance and contravariance for type parameters), so I'll give you that.

    However:

    • Objects being value types by default is pretty useful. If you create a polymorphic base class, the first thing you do is define a virtual destructor and delete the copy constructor, so slicing shouldn't be an issue in practice. Especially since polymorphic types are usually passed as references or (smart) pointers anyways and since any good linter will warn you if you manage to slice an object.
    • The modularity and ABI compatibility problems you describe have nothing to do with OOP. Actually, the fact that you mistakenly think modularity is inherently tied to OOP is a bit worrying. The biggest practical problem with C++ not having a standardized ABI is that you cannot (or, rather, shouldn't) pass STL types to and from DLLs and that you're basically limited to C APIs for dynamic libraries. But since you usually don't link that many libraries dynamically and since you can provide library headers that wrap the low-level API and provide a better interface on top, it's not the end of the world.
    • Templates are incredibly useful, too, as a tool for code generation and making decisions at compile-time. I have written a lot of code in programming languages that didn't have them and definitely missed them there.


  • @dfdub said in OOP is dead:

    @Mason_Wheeler
    I wouldn't count generics as an integral part of OOP, but they're definitely useful (especially when the language supports covariance and contravariance for type parameters), so I'll give you that.

    However:

    • Objects being value types by default is pretty useful. If you create a polymorphic base class, the first thing you do is define a virtual destructor and delete the copy constructor, so slicing shouldn't be an issue in practice.

    If you don't write a custom copy constructor, one will be automatically generated for you.

    Especially since polymorphic types are usually passed as references or (smart) pointers anyways and since any good linter will warn you if you manage to slice an object.

    So many weasel words here to dance around the simple fact that pass-by-value, which is the default behavior in C++, is completely broken and you need to go out of your way to get it right by setting up systems to avoid ever passing objects by value.

    • The modularity and ABI compatibility problems you describe have nothing to do with OOP. Actually, the fact that you mistakenly think modularity is inherently tied to OOP is a bit worrying.

    Why?

    The biggest practical problem with C++ not having a standardized ABI is that you cannot (or, rather, shouldn't) pass STL types to and from DLLs and that you're basically limited to C APIs for dynamic libraries. But since you usually don't link that many libraries dynamically and since you can provide library headers that wrap the low-level API and provide a better interface on top, it's not the end of the world.

    Until you need to rebuild something, and then the compiler keeps spinning until the end of the world arrives. :trollface:

    • Templates are incredibly useful, too, as a tool for code generation and making decisions at compile-time. I have written a lot of code in programming languages that didn't have them and definitely missed them there.

    The concept of metaprogramming is incredibly useful. I've used it to good effect in better languages. But C++ templates are a terrible implementation of that concept.



  • @Mason_Wheeler said in OOP is dead:

    If you don't write a custom copy constructor, one will be automatically generated for you.

    You don't know what deleting the copy constructor means, do you?

    Especially since polymorphic types are usually passed as references or (smart) pointers anyways and since any good linter will warn you if you manage to slice an object.

    So many weasel words here to dance around the simple fact that pass-by-value, which is the default behavior in C++, is completely broken and you need to go out of your way to get it right by setting up systems to avoid ever passing objects by value.

    What? Since when is giving the user the choice between value and reference semantics a bad thing?

    And taking away the first choice to prevent misuse is easy: If your class can't be copied or moved, it cannot be passed by value. The problem you're seeing here is not present in practice.

    • The modularity and ABI compatibility problems you describe have nothing to do with OOP. Actually, the fact that you mistakenly think modularity is inherently tied to OOP is a bit worrying.

    Why?

    Because treating OOP as the only way to achieve modularity smells a bit like cargo-cult. OOP and modularity are mostly orthogonal.

    The biggest practical problem with C++ not having a standardized ABI is that you cannot (or, rather, shouldn't) pass STL types to and from DLLs and that you're basically limited to C APIs for dynamic libraries. But since you usually don't link that many libraries dynamically and since you can provide library headers that wrap the low-level API and provide a better interface on top, it's not the end of the world.

    Until you need to rebuild something, and then the compiler keeps spinning until the end of the world arrives. :trollface:

    You're aware that static libraries are also compilation artifacts that will be cached, right? It really doesn't matter whether the file ending is .lib or .dll of all you're worried about is not having to re-compile it.

    • Templates are incredibly useful, too, as a tool for code generation and making decisions at compile-time. I have written a lot of code in programming languages that didn't have them and definitely missed them there.

    The concept of metaprogramming is incredibly useful. I've used it to good effect in better languages. But C++ templates are a terrible implementation of that concept.

    I won't defend the particular implementation, but templates have become a lot more usable in the later C++ standards and provide functionality that is simply not present in most programming languages.


  • Banned

    @Zenith said in OOP is dead:

    @dfdub said in OOP is dead:

    @Zenith said in OOP is dead:

    Not when you have 70 service classes that are all reimplementing the same dozen utility functions every single time.

    But code re-use is exactly the wrong reason to use inheritance and the easiest way to break the LSP. What you need are separate utility classes.

    And then you overspecialize your utility classes.

    Devs being bad at structuring code is a people problem. No language, no library, no paradigm will help you with a people problem.



  • @dfdub said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    If you don't write a custom copy constructor, one will be automatically generated for you.

    You don't know what deleting the copy constructor means, do you?

    It means taking the copy constructor and deleting the code to it. If there's some other act you're thinking of with some other meaning, use the words that mean that thing to describe it.

    Especially since polymorphic types are usually passed as references or (smart) pointers anyways and since any good linter will warn you if you manage to slice an object.

    So many weasel words here to dance around the simple fact that pass-by-value, which is the default behavior in C++, is completely broken and you need to go out of your way to get it right by setting up systems to avoid ever passing objects by value.

    What? Since when is giving the user the choice between value and reference semantics a bad thing?

    Having a choice between value and reference semantics is definitely a good thing. Having a choice to mix value semantics and inheritance is not, though, because that yields a semantically invalid type. It should not be permitted by the language for the same reason as the language forbids any other semantically invalid operation.

    And taking away the first choice to prevent misuse is easy: If your class can't be copied or moved, it cannot be passed by value. The problem you're seeing here is not present in practice.

    Which is why we totally don't have a term like "object slicing" to describe the real problem that it creates in practice. Oh wait, yes we do...

    • The modularity and ABI compatibility problems you describe have nothing to do with OOP. Actually, the fact that you mistakenly think modularity is inherently tied to OOP is a bit worrying.

    Why?

    Because treating OOP as the only way to achieve modularity smells a bit like cargo-cult. OOP and modularity are mostly orthogonal.

    Where did I say "the only way"? I said nothing about exclusivity; I said that modularity is a key advantage of OOP.

    You're aware that static libraries are also compilation artifacts that will be cached, right? It really doesn't matter whether the file ending is .lib or .dll of all you're worried about is not having to re-compile it.

    If I have a C# DLL, I can add a private field to a class, change the implementation of some of the methods, rebuild it, and redeploy it, and everything will still work fine. Same I have a Delphi DPK package, or a Java JAR file.

    The same cannot be said for C++. Change one little detail and you have to rebuild the entire transitive set of downstream dependencies.

    • Templates are incredibly useful, too, as a tool for code generation and making decisions at compile-time. I have written a lot of code in programming languages that didn't have them and definitely missed them there.

    The concept of metaprogramming is incredibly useful. I've used it to good effect in better languages. But C++ templates are a terrible implementation of that concept.

    I won't defend the particular implementation, but templates have become a lot more usable in the later C++ standards and provide functionality that is simply not present in most programming languages.

    Well, the particular implementation is what I have a problem with. It's a bad idea at a conceptual level. Just for starters, good metaprogramming is done in the same language, not in some poorly designed DSL embedded in the compiler.


  • BINNED

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    OOP has taken over the world and become the default, used by virtually all professional programmers the world over for any and all types of projects, because it works so well and is so easy to grasp.

    Now explain JavaScript.

    Natural monopoly that rode HTML's coattails to prominence.

    OK and now explain Node. By now JS has become the leading programming language for all kinds of server-side programs, as well as native desktop and mobile apps, and none of that has anything to do with HTML.

    An over-abundance of JS monkey idiots (not the people who made Node, those are clearly smart) that have shiny* hammer?

    *bloated, ugly, slow


  • Banned

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.



  • @topspin said in OOP is dead:

    An over-abundance of JS monkey idiots (not the people who made Node, those are clearly smart)

    3d71aca0-b4fd-49e6-b829-cb74d12d613e-image.png


  • Banned

    @Mason_Wheeler but they did, and it was utterly retarded, and it got immensely popular and this time it was without any monopolies helping it. Thank you for helping me prove my point.



  • @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.

    Any language can do that. You can do that in C. I've contributed to libraries that did it in C. But without language-level support, you can't reliably do Liskov substitution, which is why I say that Liskov substitution is the sine qua non of an OO language.


  • Banned

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.

    Any language can do that.

    Nope.

    You can do that in C.

    Nope. All functions are free-floating and there's no distinction between an object's own method and a function that simply happens to take the object as one of its arguments.

    I've contributed to libraries that did it in C.

    "Simulated" is a better word. You can simulate OOP in C, just like you can simulate RAII by always remembering to call resource-freeing functions. But C has no OOP. It has no methods, and you can't have OOP without methods, much like you can't have RAII without destructors.

    But without language-level support, you can't reliably do Liskov substitution

    Of course you can. Just make a struct of function pointers and poof - you have a compile-time guarantee that all the needed functions are in place.

    Also, JS absolutely is OOP but doesn't have any language-level support for ensuring reliable Liskov substitution. So your "sine qua non" is neither sufficient nor necessary - it couldn't be any more wrong.



  • @Mason_Wheeler said in OOP is dead:

    @dfdub said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    If you don't write a custom copy constructor, one will be automatically generated for you.

    You don't know what deleting the copy constructor means, do you?

    It means taking the copy constructor and deleting the code to it. If there's some other act you're thinking of with some other meaning, use the words that mean that thing to describe it.

    I'll use code instead:

    struct Foo
    {
      Foo(const Foo& other) = delete;
    };
    

    That's what C++ programmers mean when they say "deleting the copy constructor".

    Especially since polymorphic types are usually passed as references or (smart) pointers anyways and since any good linter will warn you if you manage to slice an object.

    So many weasel words here to dance around the simple fact that pass-by-value, which is the default behavior in C++, is completely broken and you need to go out of your way to get it right by setting up systems to avoid ever passing objects by value.

    What? Since when is giving the user the choice between value and reference semantics a bad thing?

    Having a choice between value and reference semantics is definitely a good thing. Having a choice to mix value semantics and inheritance is not, though, because that yields a semantically invalid type. It should not be permitted by the language for the same reason as the language forbids any other semantically invalid operation.

    OK, so your point is that the language should enforce what the linters already do. That's a valid point, I guess, although I'm pretty sure we're missing some crazy use case where slicing a polymorphic object can actually make sense. (Because there's a use case for pretty much every way C++ provides to shoot yourself in the foot.)

    And taking away the first choice to prevent misuse is easy: If your class can't be copied or moved, it cannot be passed by value. The problem you're seeing here is not present in practice.

    Which is why we totally don't have a term like "object slicing" to describe the real problem that it creates in practice. Oh wait, yes we do...

    Writing new C++ code in 2020 without using clang-tidy is madness. Not deleting the copy constructor of polymorphic base classes is also madness and against pretty much every guideline ever written. So running into this problem in 2020 certainly requires madness. :half-trolling:

    But really, what you're complaining about here is the general flexibility of C++ and its abundance of foot-guns, not its implementation of OOP.

    You're aware that static libraries are also compilation artifacts that will be cached, right? It really doesn't matter whether the file ending is .lib or .dll of all you're worried about is not having to re-compile it.

    If I have a C# DLL, I can add a private field to a class, change the implementation of some of the methods, rebuild it, and redeploy it, and everything will still work fine. Same I have a Delphi DPK package, or a Java JAR file.

    The same cannot be said for C++. Change one little detail and you have to rebuild the entire transitive set of downstream dependencies.

    That's incorrect. You can change things inside a C++ DLL that exposes a C API (as most of them do, see above) as well while maintaining ABI compatibility.

    And thanks to C++20's modules, we will soon finally have the luxury of not having to re-compile all dependencies for every header change inside our projects as well. Yay!



  • @dfdub said in OOP is dead:

    So running into this problem in 2020 certainly requires madness. :half-trolling:

    Since it's a C++ problem, that's a prerequisite



  • @dfdub said in OOP is dead:

    I'll use code instead:

    struct Foo
    {
      Foo(const Foo& other) = delete;
    };
    

    That's what C++ programmers mean when they say "deleting the copy constructor".

    ...wow. Not sure which is the bigger :trwtf:: the fact that that syntax exists, or the fact that it's necessary in the first place.

    Writing new C++ code in 2020 without using clang-tidy is madness. Not deleting the copy constructor of polymorphic base classes is also madness and against pretty much every guideline ever written. So running into this problem in 2020 certainly requires madness. :half-trolling:

    Yes, we already established that it requires using C++. :half-trolling:

    If I have a C# DLL, I can add a private field to a class, change the implementation of some of the methods, rebuild it, and redeploy it, and everything will still work fine. Same I have a Delphi DPK package, or a Java JAR file.

    The same cannot be said for C++. Change one little detail and you have to rebuild the entire transitive set of downstream dependencies.

    That's incorrect. You can change things inside a C++ DLL that exposes a C API (as most of them do, see above) as well while maintaining ABI compatibility.

    But then you don't have an OO interface; you have a C API.

    And thanks to C++20's modules, we will soon finally have the luxury of not having to re-compile all dependencies for every header change inside our projects as well. Yay!

    Well, I suppose "better 35 years late than never" is :technically-correct:...


  • Banned

    @Mason_Wheeler said in OOP is dead:

    @dfdub said in OOP is dead:

    I'll use code instead:

    struct Foo
    {
      Foo(const Foo& other) = delete;
    };
    

    That's what C++ programmers mean when they say "deleting the copy constructor".

    ...wow. Not sure which is the bigger :trwtf:: the fact that that syntax exists, or the fact that it's necessary in the first place.

    The biggest WTF is that it's 2020, you pose as an expert on everything wrong with C++, and you only just learned about deleting default methods.



  • @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.

    Any language can do that.

    Nope.

    You can do that in C.

    Nope. All functions are free-floating and there's no distinction between an object's own method and a function that simply happens to take the object as one of its arguments.

    The existence of extension methods makes that distinction a lot less clear cut than you're trying to frame it as here.

    But without language-level support, you can't reliably do Liskov substitution

    Of course you can. Just make a struct of function pointers and poof - you have a compile-time guarantee that all the needed functions are in place.

    Please keep in mind that I said "reliably." Coding vtables by hand rather than having the compiler build them for you is the exact opposite of reliable, because it's all too easy to miss an update or get the wrong thing in the table due to a copy-paste error. (Again, I have seen this happen in real-world code.)

    Also, JS absolutely is OOP

    :laugh-harder:


  • BINNED

    I’ve read some valid complaints here about things wrong with C++, but not really about how C++ gets OOP wrong. Other than “you can do things I don’t want to do.”

    Templates aren’t a bad alternative to generics, templates are awesome. Now, the point that they’re horribly complicated and there’s nicer implementations of meta-programming, sure that’s true.



  • @Mason_Wheeler said in OOP is dead:

    @dfdub said in OOP is dead:

    I'll use code instead:

    struct Foo
    {
      Foo(const Foo& other) = delete;
    };
    

    That's what C++ programmers mean when they say "deleting the copy constructor".

    ...wow. Not sure which is the bigger :trwtf:: the fact that that syntax exists, or the fact that it's necessary in the first place.

    It may seem that way at first, but:

    • It's a logical consequence of having compiler-generated special functions in the first place and
    • Deleting functions is actually a quite powerful tool, because you can do it with any function. If you're asking yourself why that could possibly be useful: Because deleted functions are still declared and therefore participate in overload resolution. Which means that in a bunch of cases, you can make calling a function with the wrong arguments a compile-time error, and that is actually quite neat.

    If I have a C# DLL, I can add a private field to a class, change the implementation of some of the methods, rebuild it, and redeploy it, and everything will still work fine. Same I have a Delphi DPK package, or a Java JAR file.

    The same cannot be said for C++. Change one little detail and you have to rebuild the entire transitive set of downstream dependencies.

    That's incorrect. You can change things inside a C++ DLL that exposes a C API (as most of them do, see above) as well while maintaining ABI compatibility.

    But then you don't have an OO interface; you have a C API.

    Which also has its benefits, since it makes the DLL usable from pretty much every programming language ever. And the easy "fix" is translating it back into an OO interface in the library headers.



  • @topspin said in OOP is dead:

    Templates aren’t a bad alternative to generics, templates are awesome.

    Templates are just different from generics. There's some overlap, but there are many use cases that only co- and contravariant generics can support and other use cases that only templates can support.



  • @dfdub said in OOP is dead:

    ...wow. Not sure which is the bigger :trwtf:: the fact that that syntax exists, or the fact that it's necessary in the first place.

    It may seem that way at first, but:

    • It's a logical consequence of having compiler-generated special functions in the first place

    So you're coming down on the "exists" side.

    • Deleting functions is actually a quite powerful tool, because you can do it with any function. If you're asking yourself why that could possibly be useful: Because deleted functions are still declared and therefore participate in overload resolution. Which means that in a bunch of cases, you can make calling a function with the wrong arguments a compile-time error, and that is actually quite neat.

    :thonking: "Making calling a function with the wrong arguments a compile-time error" is a standard feature of every non-toy programming language ever written. It takes a real special kind of mess to look at this and say "we've got a special feature that enables us to do this! Isn't that cool?"


  • BINNED

    @dfdub said in OOP is dead:

    Which means that in a bunch of cases, you can make calling a function with the wrong arguments a compile-time error, and that is actually quite neat.

    Huh, I never considered that. I guess that’s only useful if the arguments were otherwise convertible to a non-deleted overload?

    Can you give a rough use case?


  • Banned

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.

    Any language can do that.

    Nope.

    You can do that in C.

    Nope. All functions are free-floating and there's no distinction between an object's own method and a function that simply happens to take the object as one of its arguments.

    The existence of extension methods makes that distinction a lot less clear cut than you're trying to frame it as here.

    Extension methods are free functions with different call syntax. They can't access object's private parts, they can't be overridden, and they aren't bundled with the object wherever it goes. They're methods in name only.

    But without language-level support, you can't reliably do Liskov substitution

    Of course you can. Just make a struct of function pointers and poof - you have a compile-time guarantee that all the needed functions are in place.

    Please keep in mind that I said "reliably."

    Do you want to discuss facts, or do you want an endless argument about just how much reliability exactly is reliable enough? Because having the same interface doesn't guarantee that the method will behave identically in every externally perceivable way, and behaving identically in every externally perceivable way is what the Liskov substitution principle is about. LSP isn't a language property, it's a design goal. All that languages can do is make it easier to achieve; but they'll never be able to guarantee anything.

    Also, JS absolutely is OOP

    :laugh-harder:

    Who do you consider an authoritative source on which languages are OOP and which aren't? Wikipedia? Google presentations? O'RLY books? Give me your favorite source, I'll find you a quote that JS is an object-oriented language.


  • Trolleybus Mechanic

    @Mason_Wheeler said in OOP is dead:

    @dfdub said in OOP is dead:

    ...wow. Not sure which is the bigger :trwtf:: the fact that that syntax exists, or the fact that it's necessary in the first place.

    It may seem that way at first, but:

    • It's a logical consequence of having compiler-generated special functions in the first place

    So you're coming down on the "exists" side.

    • Deleting functions is actually a quite powerful tool, because you can do it with any function. If you're asking yourself why that could possibly be useful: Because deleted functions are still declared and therefore participate in overload resolution. Which means that in a bunch of cases, you can make calling a function with the wrong arguments a compile-time error, and that is actually quite neat.

    :thonking: "Making calling a function with the wrong arguments a compile-time error" is a standard feature of every non-toy programming language ever written. It takes a real special kind of mess to look at this and say "we've got a special feature that enables us to do this! Isn't that cool?"

    Yeah I didn't quite get that reason since what he described is how I'd expect any statically typed language to work.


  • BINNED

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    Also, JS absolutely is OOP

    :laugh-harder:

    Who do you consider an authoritative source on which languages are OOP and which aren't? Wikipedia? Google presentations? O'RLY books? Give me your favorite source, I'll find you a quote that JS is an object-oriented language.

    I think he interprets it as OOP === class-based OOP.


  • Trolleybus Mechanic

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    The core sine qua non principle of OOP is inheritance and Liskov substitution.

    No you fucki...

    Wait we're not in garage.

    So just no. Just no. The sine qua non principle of OOP is objects that entangle data and behavior into a single atomic unit. Everything else is fluff.

    Any language can do that.

    Nope.

    You can do that in C.

    Nope. All functions are free-floating and there's no distinction between an object's own method and a function that simply happens to take the object as one of its arguments.

    I've contributed to libraries that did it in C.

    "Simulated" is a better word. You can simulate OOP in C, just like you can simulate RAII by always remembering to call resource-freeing functions. But C has no OOP. It has no methods, and you can't have OOP without methods, much like you can't have RAII without destructors.

    But without language-level support, you can't reliably do Liskov substitution

    Of course you can. Just make a struct of function pointers and poof - you have a compile-time guarantee that all the needed functions are in place.

    Also, JS absolutely is OOP but doesn't have any language-level support for ensuring reliable Liskov substitution. So your "sine qua non" is neither sufficient nor necessary - it couldn't be any more wrong.

    You can do OOP in C, conceptually. You just don't have direct language syntax helping you do it.

    Said another way:

    • C is not an OOP language
    • You can do OOP in C


  • @topspin said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    Also, JS absolutely is OOP

    :laugh-harder:

    Who do you consider an authoritative source on which languages are OOP and which aren't? Wikipedia? Google presentations? O'RLY books? Give me your favorite source, I'll find you a quote that JS is an object-oriented language.

    I think he interprets it as OOP === class-based OOP.

    Yes. OOP was invented in the 1960s by the Simula team. It had classes and instantiation of objects. It had inheritance. It had private, protected, and public members. It defined what OOP is, and it got the basic concept so right that future iterations have seen very little need to deviate from it. (The biggest innovations on the original Simula model have been the addition of stuff like Properties and Events.)

    The term "object-oriented" was invented later by Alan Kay, who attempted to co-opt the popularity of objects and redefine what they are and the interaction between them on a completely different model. I hold this attempt in the same regard as I do any other attempt to redefine well-understood terms with a well-established meaning for political gain.


  • Banned

    @topspin said in OOP is dead:

    I’ve read some valid complaints here about things wrong with C++, but not really about how C++ gets OOP wrong. Other than “you can do things I don’t want to do.”

    Quick rundown from someone who doesn't arbitrarily exclude JS from the family of OO languages:

    • For inheritance to work properly, the base class needs a virtual destructor and no copy constructor. C++ defaults to a non-virtual destructor and an autogenerated copy constructor.
    • Multiple inheritance sounds nice in theory but is a total shitfest in practice. And the details of how virtual inheritance works would make a good basis for an H. P. Lovecraft book.
    • Whoever came up with the idea of access modifiers on inheritance declarations - and defaulting to private - must have had a brain tumor.

  • Trolleybus Mechanic

    @Gąska said in OOP is dead:

    @topspin said in OOP is dead:

    I’ve read some valid complaints here about things wrong with C++, but not really about how C++ gets OOP wrong. Other than “you can do things I don’t want to do.”

    Quick rundown from someone who doesn't arbitrarily exclude JS from the family of OO languages:

    • For inheritance to work properly, the base class needs a virtual destructor and no copy constructor. C++ defaults to a non-virtual destructor and an autogenerated copy constructor.
    • Multiple inheritance sounds nice in theory but is a total shitfest in practice. And the details of how virtual inheritance works would make a good basis for an H. P. Lovecraft book.
    • Whoever came up with the idea of access modifiers on inheritance declarations - and defaulting to private - must have had a brain tumor.

    Multiple inheritance can be implemented reasonably well, you just need to actually define it. For instance the common cake pattern in scala generally works well and the language defines what happens when you mix in several traits that all define the same method.


  • Banned

    @Mason_Wheeler said in OOP is dead:

    It defined what OOP is, and it got the basic concept so right that future iterations have seen very little need to deviate from it.

    Up until people figured out that it would be nice to make a class implement additional interfaces without having to modify the class's definition, and now everything sucks hard compared to Rust. But considering how slow you were to learn about C++11's most important change, I'll give you two more decades to figure this one out.


  • BINNED

    @Gąska said in OOP is dead:

    @topspin said in OOP is dead:

    I’ve read some valid complaints here about things wrong with C++, but not really about how C++ gets OOP wrong. Other than “you can do things I don’t want to do.”

    Quick rundown from someone who doesn't arbitrarily exclude JS from the family of OO languages:

    • For inheritance to work properly, the base class needs a virtual destructor and no copy constructor. C++ defaults to a non-virtual destructor and an autogenerated copy constructor.

    Only true for polymorphic objects.

    • Multiple inheritance sounds nice in theory but is a total shitfest in practice. And the details of how virtual inheritance works would make a good basis for an H. P. Lovecraft book.
    • Whoever came up with the idea of access modifiers on inheritance declarations - and defaulting to private - must have had a brain tumor.

    I use multiple inheritance all the time (hyperbole). Private inheritance makes that actually useful, as it doesn’t make the derived class a sub-type of the base class (at least from outside), but an invisible implementation detail. It’s basically “composition over inheritance” without the boilerplate. (I guess newer languages have simpler ways to do that, though)

    Now, if you got virtual inheritance, you‘ve got yourself into a big problem.
    I’ve used that once in a past life. I don’t remember why I needed it or how it should’ve been solved in a better way, but I’m sure it was badly designed.



  • @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    It defined what OOP is, and it got the basic concept so right that future iterations have seen very little need to deviate from it.

    Up until people figured out that it would be nice to make a class implement additional interfaces without having to modify the class's definition, and now everything sucks hard compared to Rust.

    Rust? To me that sure sounds like the most significant misfeature in the Go language. (And why are you talking about classes in Rust when no such thing exists, which is one of the main reasons why Rust sucks hard compared to everything.

    But considering how slow you were to learn about C++11's most important change, I'll give you two more decades to figure this one out.

    I gave up on C++ long before '11 rolled around. The language is fatally flawed and can't be fixed without breaking all-important backwards compatibility.

    When they come up with a new version where char* hello_world = "hello " + "world"; does what you would intuitively expect, rather than producing a mess of gibberish that requires an in-depth understanding of pointer arithmetic to make sense of, then we'll talk. But AFAIK in 35 years they haven't even been able to fix that, let alone its bigger problems.


  • BINNED

    @Mason_Wheeler said in OOP is dead:

    When they come up with a new version where char* hello_world = "hello " + "world"; does what you would intuitively expect, rather than producing a mess of gibberish

    It doesn't even compile. The error message is pretty clear:

    error: invalid operands to binary expression ('const char [7]' and 'const char [6]')
    char* hello_world = "hello " + "world";
                        ~~~~~~~~ ^ ~~~~~~~
    

    This does exactly what you'd expect:

    #include <iostream>
    #include <string>
    using namespace std::string_literals;
    
    int main()
    {
        auto hello_world = "hello "s + "world"s;
        std::cout << hello_world << std::endl;
    }
    

    that requires an in-depth understanding of pointer arithmetic to make sense of, then we'll talk.

    I assume you understand pointer arithmetic, so this isn't directed at you, but I can't take anyone seriously who whines "pointers are hard, I don't understand that". Pointers are low-level, often too low-level for the abstraction required, but not hard. That's like first term stuff. It's basically the same as saying "I don't understand indices."


  • Banned

    @Mason_Wheeler said in OOP is dead:

    @Gąska said in OOP is dead:

    @Mason_Wheeler said in OOP is dead:

    It defined what OOP is, and it got the basic concept so right that future iterations have seen very little need to deviate from it.

    Up until people figured out that it would be nice to make a class implement additional interfaces without having to modify the class's definition, and now everything sucks hard compared to Rust.

    Rust? To me that sure sounds like the most significant misfeature in the Go language.

    Because Go sucks, and Go interfaces suck, and the very idea behind Go interfaces sucks so much it couldn't possibly be implemented in any language in a way that doesn't suck. Rust, on the other hand, has good design and good implementation.

    Basically - you like extension methods, don't you? Imagine if extension methods could be used to turn a non-enumerable class into an IEnumerable.

    And why are you talking about classes in Rust when no such thing exists

    Because even without classes, Rust objects are much better at designing object-oriented APIs than Java objects ever were and ever will be.

    which is one of the main reasons why Rust sucks

    Have you ever used Rust? Why do I even ask. Of course you haven't.

    But considering how slow you were to learn about C++11's most important change, I'll give you two more decades to figure this one out.

    I gave up on C++ long before '11 rolled around.

    So how about you shut the fuck up about things you have no idea about?


  • Discourse touched me in a no-no place

    @Mason_Wheeler said in OOP is dead:

    Basically "in every way." Just off the top of my head:

    • The core sine qua non principle of OOP is inheritance and Liskov substitution. C++ makes objects value types rather than reference types, in open defiance of every OO language before and since, which breaks Liskov substitution 6 ways from Sunday due to slicing problems.

    Technically, no. The actual core principle of OOP is encapsulation; there is literally nothing more important about OOP than that. Inheritance is a second-rank feature.

    Fortunately for your larger argument, C++ is terrible at encapsulation too, and for the same reason you cite. 😆 All those value types break encapsulation, with it typically being necessary for a type to be wholly understood in all its particulars in order for it to be used at all. That makes APIs in C++ extremely fragile, and just causes pain upon pain. If we compare with many other languages, they have stronger encapsulation and so it isn't necessary to know nearly as much about an object in order to use it.

    • One of the key advantages of OOP is modularity and reuse. But C++ inherits C's abysmal #include system, providing terrible modularity at the source file level, and just to make things worse, provides no standard ABI, meaning there's no modularity and reuse at the DLL level as well.

    C's #include system is very simple. And very simple-minded too. But C is itself close to as simple minded as a language can be while supporting structured programming, and that's where it deliberately sits in the world, first sane-ish stop above assembler.

    C++ tries to be C. And a much higher level language at the same time. It's got linguistic multiple personality disorder.

    • Generics are an important part of modern programming. C++'s templates are perhaps the worst possible way of implementing generics: a separate language within the main programming language, describing programs that must be evaluated to completion for compilation to progress, that happens to be Turing-complete by accident so you can't actually prove that that will ever happen!

    Agreed.

    If anyone wishes to dispute that, they first need to ask why no other, newer programming language does templates the same way as C++. It's not like the power of them is unknown, or that there are no languages newer than the first version of C++ where the power of templates became widely known. No, language designers and implementers — generally a pretty smart bunch of people — are actively avoiding doing things that way. All of them are making the same decision. :thonking:


  • Trolleybus Mechanic

    @dkf said in OOP is dead:

    All those value types break encapsulation, with it typically being necessary for a type to be wholly understood in all its particulars in order for it to be used at all.

    I'm not following how the storage location (stack vs heap) changes anything. Especially when you can get a reference/pointer to a stack stored object.


  • Discourse touched me in a no-no place

    @mikehurley said in OOP is dead:

    I'm not following how the storage location (stack vs heap) changes anything. Especially when you can get a reference/pointer to a stack stored object.

    Actually, the core of the problem is the fact that everything is resolved during compilation into direct function calls or fairly fixed offsets into the memory structure or offsets into the vtable. That makes everything very inflexible. In terms of ABI flexibility, every class that has part of its structure precompiled and part defined at use time (inline bits and pieces, or direct access to fields) is part of the problem.

    Yes, there are cases where exact memory structures are needed. They're almost never associated with objects where any kind of inheritance is necessary.



  • @Gąska said in OOP is dead:

    No language, no library, no paradigm will help you with a people problem.

    Nor will a forum software. But that doesn't stop people:doing_it_wrong: from trying.



  • @Mason_Wheeler said in OOP is dead:

    Making calling a function with the wrong arguments a compile-time error" is a standard feature of every non-toy programming language ever written.

    Ugh, I should have known you'd make a snarky comment. Obviously, C++ is statically typed and already makes sure arguments are type-compatible. But sometimes you want to forbid arguments even though their types would be compatible with the signature.

    @mikehurley said in OOP is dead:

    Yeah I didn't quite get that reason since what he described is how I'd expect any statically typed language to work.

    The answer is pretty much this:

    @topspin said in OOP is dead:

    I guess that’s only useful if the arguments were otherwise convertible to a non-deleted overload?

    Here's an artificial example:

    unsigned int factorial(unsigned int n)
    {
      if (n == 0) {
        return 1;
      }
      return n * factorial(n - 1);
    }
    

    You don't want anyone to accidentally call your factorial function with a signed integer argument (because you want to avoid cases where the integer variable is accidentally negative) and you certainly don't want anyone to call it with a double argument, thinking it'll do something useful for floating point values. These conversions can be prevented by defining deleted overloads:

    unsigned int factorial(int) = delete;
    unsigned int factorial(double) = delete;
    

    The same way, you can prevent all kinds of otherwise implicit conversions, not just the conversions between built-in types that C++ unfortunately inherited from C. Sometimes, you want certain user-defined types to be convertible in most places, but prevent that conversion in a specific place.

    A probably slightly more common use case: You can also use this feature to prevent dangling references. Let's say you have a class ConfigurationFile that represents what you read from your configuration. Certain configuration keys are optional, so provide the following method:

    struct ConfigurationFile {
      const std::string& getOrDefault(const std::string& key, const std::string& defaultValue);
    };
    

    Looks reasonably, but now you've made it easy to write buggy code:

    auto& value = myFile.getOrDefault("foo"s, "bar"s);
    

    The first argument is fine, but the second argument absolutely shouldn't be a temporary, because if the configuration file doesn't contain a value for foo, the return value of getOrDefault() is a dangling reference to where the temporary string used to be.

    struct ConfigurationFile {
      const std::string& getOrDefault(const std::string& key, const std::string& defaultValue);
      const std::string& getOrDefault(const std::string&, const std::string&&) = delete; // fixed!
    };
    

    Now you've made sure to warn the user with a compile-time error if they decide to do something dumb.



  • @Gąska said in OOP is dead:

    Multiple inheritance sounds nice in theory but is a total shitfest in practice. And the details of how virtual inheritance works would make a good basis for an H. P. Lovecraft book.

    This is the argument I expected to hear, but I would also disagree with that. Multiple inheritance is perfectly fine unless you try to do dumb things with it. And inheriting from two classes that non-privately inherit from a common base class and then expecting to be able to easily call methods on that base class from the derived class is definitely a dumb thing.

    People fixate way too much on the potential diamond problem, but I have yet to see a case where this caused an actual issue in practice. If you're wondering whether you should introduce virtual inheritance into a class hierarchy, you should probably stop what you're doing, nuke your existing code from orbit and come up with a better design.



  • @topspin said in OOP is dead:

    pointer arithmetic

    Serious question: When's the last time you even used pointer arithmetic?

    In a modern (≥ C++11) code base, if you're doing pointer arithmetic at all, that's probably a code smell.



  • @dkf said in OOP is dead:

    All those value types break encapsulation, with it typically being necessary for a type to be wholly understood in all its particulars in order for it to be used at all. That makes APIs in C++ extremely fragile, and just causes pain upon pain.

    Well, yeah, if you want to treat an object as a value in an AOT-compiled language, you obviously need to know its size, which requires knowing about its private members. That's by design, because you have to make a trade-off between flexibility / compilation time and compiler optimizations / performance. C++ strongly favors the latter.

    If we compare with many other languages, they have stronger encapsulation and so it isn't necessary to know nearly as much about an object in order to use it.

    But they also don't have the same performance characteristics as C++ and a more heavyweight runtime.

    Is that annoying at times? Sure, but I don't see a way around it without completely changing the design goals and purpose of the language.

    @dkf said in OOP is dead:

    Actually, the core of the problem is the fact that everything is resolved during compilation into direct function calls or fairly fixed offsets into the memory structure or offsets into the vtable. That makes everything very inflexible.

    Well, yeah, but also performant.


Log in to reply