Ask HN: Why do new(ish) programming languages eschew OOP features?
Some relatively new or popular languages are not object-oriented, although they may have object-like features. Examples include: Rust, Go, Julia, Nim.
I have always struggled with OOP and have never found it a natural way of programming. But many other programmers will disagree of course.
I find it fascinating that some new languages have chosen to eschew the OOP model. Why do you think that is? And what do think of this trend (if it is indeed a trend)?
233 comments
[ 4.7 ms ] story [ 374 ms ] threadThere are domains where OOP fits perfectly, there are domains where functional programming fits perfectly, there are even domains where imperative programming fits perfectly.
We're constantly searching for the perfect fits-it-all solution which we haven't found yet. From my POV this is why there are some competing standards, which all have their merits but on a first glance exclude each other.
There is Elixir / Erlang which many consider to stick more closely to the original idea of OOP.
There's nothing wrong with object methods (that's 100% pure syntax vs. a function call) and an implicit "this" scope for symbols (which is just a limited form of dynamic scope[1]). They don't make code hard to understand. OO can be abused to produce bad designs, of course, but that's not an indictment of its syntax.
Non-syntactic aspects are maybe a more involved discussion. For an example, I personally think traditional OO lends itself very nicely to runtime polymorphism. And this is something that more modern languages have really struggled with (take a random new hacker and try to explain to them virtual functions vs. trait objects). Now... polymorphism can be horribly abused. But, it's still useful, and IMHO the current trends are throwing it out with the bathwater.
[1] Something that itself has long since fallen out of fashion but which has real uses. Being able to reference the "current" value of a symbol (in the sense of "the current thing we are working on") is very useful.
Clean compositions, ala Scala traits, can be done with OO. It's just not something I've seen in the wild, or something actively encouraged by any OO teachings I've witnessed. At the same time, composition is pretty much the only way FP is taught (at least in my experience).
UI heavily relies on a large number of conventions that the user learns to expect to cat in certain ways. You can parameterize a UI widget that encapsulates these conventions with function pointers or overridden methods, but the end result is pretty much the same.
I'll strongly agree however that far too few programmers in OO languages pay enough attention to the hard problem of designing for extension. It's the reason that C#, unlike Java, defaults to non-virtual methods.
In PHP for example all query builders are meant to be used as a tree of objects, which means if you want to recursively change the table names used by the query you're out of luck because that state is often private and behaviour not implemented
Or maybe you want to inspect the query as data at runtime or assert against its shape or deeply update or delete or copy or compose parts of it all of those things are difficult
But use a data oriented library and suddenly they're easy transparent and predictable, self plug : https://github.com/slifin/beeline-php
See hiccup for a data oriented UI HTML tree, honeySQL for SQL there's one for css, routing, there's some for all of the state of your app like redux and Integrant
As soon as you have a tree of objects your coworkers won't know how your objects work till you train them and even then it won't be as powerful as your standard library, but your coworkers do know how to manipulate arrays vectors maps sets dictionaries etc self made objects are obtuse in these use cases
Objects are a bit weaker for UI as a projection of a domain data model, especially when it needs to be reactive to changes in the underlying data. The more the UI is data-bound, the more I prefer a functional / data flow idiom.
Classical OOP forces you to organize around a single and very specific taxonomy; things "in real life" are usually the very opposite of that. I remember textbook examples of inheritance with shapes or animals, both of which actually show clearly why it's a bad idea.
Man, I wonder how people went through this wondering, Who uses this? Can't they show us something real!
Eventually you just kind of tune out, because when you often meet the real world use cases, OO either descends to hierarchical verbosity from hell with layers and layers of generic abstract stuff when things should be more direct.
Nonetheless, that was in 2013, it's now 2019, video games are still written in C++ and not any FP language. FP has been "the future" for as long as I've been alive and I don't think it'll ever happen.
I don't code Scala, but aren't these traits approximately same as interfaces in C#? Interfaces are used a lot in the wild, including in the standard library, they're often generic, IEnumerable<T>, IComparable<T>, and so on.
Of course you can separate concerns properly in an OO context, but most developers don't. It's much easier to consider properly when the entire language is structured around it.
Back when Gmail just started, one of the things that it made different from other web-mail services (including hotmail) was letting you "label" emails instead of "moving them" to folders.
The problem with Directories was that, at some point, content might have two different classifications, so the question of putting it in two directories arises (if using that abstraction).
Same thing happens with Object hierarchies, even if you start meticulously defining the hierarchy of your objects given the current domain you are mapping, chances are in 2 years you will get a trait/data that does not really fall in one of your defined objects, and you will struggle to put it in one or the other, and your encapsulation will start breaking.
That happens "in practice" in real life, and is something that tons of books about OOD, OOA, and OOP define as incorrect architecture in theory, but there was always a disconnect.
OOP is inherently an imperative paradigm; you might mean “procedural” instead of “imperative” (certainly, that makes both uses of “imperative” in your post more sensible), but even then, OOP as a paradigm is very closely related to the procedural paradigm.
Although you make some good points, I disagree with the premise that it’s pure fashion. Language syntax doesn’t prevent you from implementing various OO paradigms but when you combine syntax with community norms, things do tend to be constrained. Nobody does traditional inheritance in Go, for example, which actually tends to work out well in use cases where it’s popular.
At least the top comment is about the actual topic, and not someone whining about the font color or the “clickbait” headline.
.. “Our customers wanted OO prolog so we made OO prolog” .. http://harmful.cat-v.org/software/OO_programming/why_oo_suck...
.. screw things up with any idiotic "object model" crap. .. http://harmful.cat-v.org/software/c++/linus
On the flip side, the modern language zeitgeist isn't really trying to change things in fundamental ways, except to say "don't do bad OO". But you don't need to reject OO syntax and features (c.f. polymorphism above) to reject bad OO. That part is fashion.
I disagree, I'd say all of the quoted languages are attempting to fundamentally change things to get around the expression problem[0][1]
[0] https://news.ycombinator.com/item?id=11683379
[1] https://en.m.wikipedia.org/wiki/Expression_problem
But to me the rise of OOP in the 90es where driven by the need of programming user interfaces and the rise of Windows and similar. A graphical user interface is naturally represented as a hierarchy of objects each with their own internal state. Often the language was designed to work in an integrated development environment with a user interface builder (with language features such as object serialization and reflection).
This is very obvious in Borland Delphi (OO Pascal), Objective-C, VB, Smalltalk, C#.
Then things shifted and people started doing web development where all of sudden you had hundreds of concurrent users on a single server; and then people began (re)inventing languages that handled concurrency well.
Having said that. I don't think the dominating trend today is functional programming but rather "multiparadigm" (as it should be).
OO crapola also mapped reasonably well onto GUI design. That's a whole class of programming that doesn't really exist any more. Where it does (I dunno what people use besides Qt), it looks kind of objecty.
1980 module, Modula-2
1979? package, Ada
http://www.drdobbs.com/open-source/history-and-goals-of-modu...
I agree it's fashion to some extent, and still could be done with OOP, though I think it's nice we are doing it this way (for the above reasons).
My problem with OO is one of culture. If and only if you work with big codebases you start to feel that you can’t make assumptions about anything. For all I know the plus operator can send nukes. Then they say it doesn’t matter because they made the perfect lasagna with a million layers.
I know there’s a million ways to code with oo to make it better. But I never saw that enforced by tools which is a must if we are a thousand developers in the same company.
Meanwhile if I see a concept in Haskell I can in most cases just trust the assumptions.
Also the compiler researchers that I adore are those that focus on making proofs and stable code, not the ones trying to dumb down programming to make it accessible.
You build a house, which is more important, a good foundation (built on math) or accessibility for the beginner builders?
grandparent comment is saying that OO codebases express conways law more fully (as it is definitely a spectrum).
I don't have an opinion on it either way (beyond stating that adherance to conways law is a spectrum rather than binary), just clarifying.
If you feel you can just 'trust the assumptions' in Haskell but not in OO environments you've probably just been comparing very different codebases written by very different programmers.
In a codebase that uses OOP well it's very easy to understand what assumptions you can make and the tooling can be excellent. For instance, IntelliJ will happily show you all the possible implementations of a virtual method if you're using polymorphism. "It might launch the nukes" is pure Haskell meme noise - the equivalent unexpected behaviour for Haskell would be a difficult to understand space leak.
I’ve used Agda. It’s extremely difficult. Even pretty basic proofs can be extremely tricky; just figuring out how to use the transitive equality proof syntax is a challenge. You quickly run into the need for stuff like “universe polymorphism” which comes with a huge and terrifying theory. If this is the only way to make decent programs then we’re doomed!
pun intended?
It is.
The strength of a language isn't just in what allows you to do, but also in what it limits you to do.
OO in the style of C++/C#/Java is extremely flexible in terms of code organization. This means that most codebases eventually grow towards a mess of different styles and inconsistent design patterns. One guy's abstraction is the next guy's unnecessary plumbing. Teams often have to manage consistency of style and structure through out-of-code agreements.
The advantage of paradigms like procedural or functional programming as well as trait based "OO", is that there is generally an obvious way to structure something. Two different programmers working on the same problem, are likely to produce similarly structured code. The result is that different programmers will adopt much easier to different codebases.
Is the problem perhaps that the wrong lessons were taken from earlier OOPs by later OOPs? Alan Kay in 2003:
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them.
* http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...
* https://news.ycombinator.com/item?id=19415983 (via)
Funnily enough it also describes some of my more elaborate shell scripts with various messages flowing into and out of self contained processes.
And surely that would be the way that you consider correct.
After seeing codebases with hundreds of global variables I know that OOP is not the only paradigm that can be abused.
I do believe that object orientation as a concept has value. A lot of concepts map easily to objects by nature. However the 90s OOP fashion, exemplified by Java, lead to horrible lasagna code. Especially if the underlying space didn't map straightforwardly to the concept of an object, then you're adding a layer of abstraction that can lead to misunderstandings.
Not really; in OOP a method call like `foo.bar(baz)` sends the `foo` object the message `bar` with argument `baz` (in Kay's current terminology); or, in more 'mainstream' terminology, it looks for a method in the `bar` slot of `foo` and calls it with `baz`.
As far as I'm aware, this is a pretty core concept in OOP: if our code isn't organised along these lines, then we're writing non-OOP code which just-so-happens to use classes/prototypes/methods (in the same way that we can e.g. write OOP code in C which just-so-happens to use procedures/switch/etc.).
(Side caveat: I appreciate that I'm making some assumptions here; one of my pet peeves with OOP is that it's rife with No True Scotsman fallacies about what is "proper" OOP, e.g. see 90% of the content on https://softwareengineering.stackexchange.com )
There is a fundamental asymmetry between the roles of `foo` (object, receiver of message, container of methods) and `baz` (argument, included in message, passed into method).
A function call like `bar(foo, baz)` doesn't have this asymmetry: the order of the arguments is just a convenience, it has no effect on how the `bar` symbol is resolved (in most languages it's via lexical scope, just like every other variable). Swapping them is also trivial, e.g.:
In contrast, I can't think of an OOP alternative to this which isn't messy (e.g. adding a `rab` method to `baz` via monkey-patching).Of course, the elephant in the room here is CLOS, but that's sufficiently different from most OOP as-practiced that it's better considered separately (e.g. I'd be more inclined to agree that CLOS methods are "100% pure syntax vs a function call")
> Non-syntactic aspects are maybe a more involved discussion. For an example, I personally think traditional OO lends itself very nicely to runtime polymorphism.
I also disagree with this, again due to the artificial asymmetries that OOP introduces (that objects "contain" methods). In particular, as far as I'm aware OOP simply cannot express return-type polymorphism. The asymmetry between arguments and return values is certainly more fundamental than the completely unneeded distinction between receiver and arguments, but it's still very useful to dispatch on the output rather than the input.
The classic example is `toString` being trivial in OOP (a method which takes some object as input and renders it to a string; the implementation dispatches on the given object); whilst `fromString` is hard (a method which takes a string and returns some object parsed from it; OOP can't dispatch the implementation by "looking inside" the object, since we don't have an object until we've finished parsing).
You might say this isn't OOP in the strictest possible sense, but extension methods + type inference + reified types gives what you're asking for in a way that's natural for an OOP programmer.
I'd say that's a feature, not a bug :)
> You might say this isn't OOP in the strictest possible sense
I would say it's not OOP in any sense. Having one function implement all the different behaviours, and pick which one to run by switching on some data (in this case the class tag) is a classic example of being not OOP.
As a bonus, this ignores dynamic dispatch (the only implementation is for `String`) and it's not polymorphic (the same code doesn't work for different types; instead we have different clauses for each type).
This would be salvagable if `String::fromString` only enforced the type constraint, and dispatched to `T::fromString` to do the actual parsing, e.g.
I'm not sure whether that would work or not (I've never written Kotlin), but I still think it's "messy" (monkey-patching methods on to classes, reimplementing dynamic dispatch manually, inspecting classes (via the equality check), going out of our way to prevent infinite loops, etc.)I also wouldn't use this pattern but, it is possible, and Kotlin is an OOP language.
I could elaborate, but others have said it better anyways.
https://en.m.wikipedia.org/wiki/Composition_over_inheritance
I've only fell into composition when I got into video game development. We saw it a bit more with Silverlight but now it's such a clear movement (composition over inheritance) that it's in my resume's motto.
I recall encountering Emerald in the late 1980s. Here's its 1989 paper on composition over inheritance (code reuse wise): http://www.emeraldprogramminglanguage.org/Raj_ComputerJourna...
What you can do is make composition easier. Kotlin does this with its delegation system: https://kotlinlang.org/docs/reference/delegation.html
That said I don’t claim inheritance to be useless by any means, and I would not use Go for every kind of software.
I kinda agree. Still, for an imperative language I think it's a tool I'd like to have in my toolbox, or at least it needs a compelling alternative, which I don't think Go has (1). Of course there are many cases in which you can do perfectly without.
(1) I know it has composition through struct embedding, but not real inheritance: a method delegated to the embedded struct can't call back a method on the embedder automatically. If you do know of some other means in which Go can replace this kind of inheritance, I'd be happy to hear about it.
The article you linked mentions IMHO the biggest disadvantage:
One common drawback of using composition instead of inheritance is that methods being provided by individual components may have to be implemented in the derived type, even if they are only forwarding methods
Code whose only purpose is to "appease the design" and otherwise does absolutely nothing of value is a strong-enough negative reason. It's pure bloat, overhead that gets in the way of both programmers trying to understand/debug and machines trying to execute.
Too much inheritance can lead to a "where is the method" problem, but I think that's still better than the alternative of dozens upon dozens of lines of otherwise useless code, because the former at least does not increase the amount of code that needs to be written/debugged/maintained.
(More likely, you wouldn't write this, but every example that is colloquial in inheritance is non-colloquial in composition. The composition oriented solution can be used cleanly with strong guarantees provided to its users; whereas all inheritance oriented solutions are so tacky and hard to use that you will demand a framework with dependency injection which half your team won't be able to understand and will have to treat as magical incantations.)
Note: I'm not saying that ridiculous_fish is right. But it's pretty obvious that that's the claim.
The premise was great. We reason about things as real-world objects, if we organize our code the same way, we can reason about our code.
Twenty-plus years later, In practice, however, I'm given a graphics object with a DisplayText method. It has three parameters, two of which are optional. If I call it with text I want displayed? It is an extremely rare event that what I wanted to happen, happens.
It gets much worse from this simple example, with versioning, monkey-patching, and overloading (ouch!). Add in multiple languages using the same codebase? Mutable code?
The maintenance programmer is left with a general concept. Under that concept there is code. Whether that concept maps to the concept the users asked about, or what changes in state that code makes? Nobody knows.
It's worse than bad. The incoming programmer is given concepts he thinks he can reason about, but which rarely match what's actually happening.
To combat that, the OO-mutable guys have gone to TDD. Test-first, then code.
That's a survival skill in modern programming, but all it does is change the definition of "What is a foo?" from a free-wheeling conversation to a concrete executable set of tests. And that is assuming you use it everywhere.
OO was a big-picture, conceptually-simple way of organizing code for big projects. We had a bucket for everything.
We ended up with hundreds of buckets that were confusing and we were never sure what belonged where, or if we touched one thing what other things might happen.
Functions, however, remained very simple. No matter what the function does, is it something you want or not? As long as you keep it simple and immutable, over time you keep building up and honing a reusable set of functions: tokens describing important things you want the system to do. As it turns out, quite surprisingly to me and others, reasoning and simplifying around what you want the system to do is much more productive and easier to understand than reasoning and simplifying around what you want the system to be
Take Rust. Just because I like their pragmatic approach. They represent objects as structs with functions attached that have access to the struct data. In C++, Java, etc this is roughly how objects actually work underneath.
They eliminate inheritance, replacing it with interfaces. Exposing the objects for what they really are, structs with functions attached, makes this strategy easier.
Traditional OOP, especially with multiple inheritance, tends to encourage nested objects (structs) that become hard to reason about.
Another related innovation has been structural typing. Typescript is great with this. Essentially, if you have two "objects" with the same fields, you can assign them to eachother freely. Typescript doesn't care if they actually inherit from eachother. If the interface signatures are compatible (matching fields, matching types), they can be freely used in eachothers place. This is great for constructing anonymous objects to pass off without all the cruft, basically just Json fields.
TLDR: newer languages decided that if two objects look like ducks they are both ducks, even if you decide to call one something else. Because who really cares what you name your ducks or which ducks they inherit from. This breaks from traditional OOP by loosening the rules of inheritance, but so far it's been a boon for productivity (for me at least)
Structs with functions attached is easy, and not entirely uncommon in procedural and even functional languages.
I don't see how this is not already achievable in Java or C#. No one is forcing you to use inheritance. And when you really need it, it's there for you to use instead of jumping through hoops.
There are better solutions like what Kotlin and Scala use (and potentially C# in a future version).
Who cares if it's an employee or a gun, as long as I can fire it.
Programmers have noticed that infinite abstraction was not necessarily a good thing to have (or an easy one to achieve with a good design).
They had neglected alternative paradigms and lower level languages. They had hoped to just abstract C away, and forget about it. Now they want to replace C instead of abstracting it away.
* The value of values
* Simple made easy
* Are we there yet?
I'm on mobile, but you'll find these easily on the google.
They are given by the author of Clojure, but Clojure is hardly a prerequisite to many of his talks.
In short OO gets time fundamentally wrong, is unable to represent a discrete succession of values, and entangles state with operation.
OO has been ~35 year wrong turn for the software development industry.
FP has become fashionable in recent years and now it's gone to people's heads. But some difficult facts about computer science remain:
- CPUs are much faster at reading and writing to recently used locations. Mutating in place is fast compared to constantly copying things.
- Many of the most efficient data structures and algorithms require mutable state. There are entire areas of computer science where you cannot implement them efficiently without mutable state, like hash tables.
- Constantly copying immutable objects places enormous load on the GC. This is getting better with time (there are open source ultra-low-pause GCs now), but, Rich Hickey just sort of blows this off with a comment that the "GC will clean up the no longer referenced past". Sure it will: at a price.
- He repeats the whole canard about FP being the only way to exploit parallel programming. People have been claiming this for decades and it's not true. The biggest parallel computations on the planet not so long ago were MapReduce jobs at Google: written in C++. Yes, the over-arching framework was (vaguely) FP inspired. But no actual FP languages appeared anywhere, the content of the maps and reductions were fully imperative and the MapReduce API was itself OO C++!
Also note that Java has a rather advanced parallel streams and fork/join framework for doing data parallel computation. I never once saw it used in many years of reading Java. SIMD is a much more useful technique but nearly all SIMD code is written in C++, or the result of Java auto-vectorisation. FP programming, again, doesn't have any edge here.
https://www.infoq.com/presentations/Simple-Made-Easy/
https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hi...
If you're going to watch any of these, at least watch "The Value of Values".
1. Encapsulation
2. Polymorphism
3. Inheritance
4. Abstraction
5. Code re-use
6. Design Benefits
7. Software Maintenance
8. Single responsibility principle
9. Open/closed principle
10. Interface segregation principle
11. Dependency inversion principle
12. Static type checking
He then goes on to debunk them showing alternatives from functional programming.
http://www.smashcompany.com/technology/object-oriented-progr...
Back when it seemed most people were doing Java, we expected any new language to be OO. At the time, when a new language design chose not to provide traditional OO... that could be for elegance of the language design, and (by accident or design) also a nudge to programmers, to learn how to use other concepts.
And, with some languages, the language is powerful enough that you can layer a reasonable implementation of a traditional object system atop their primitives, if you want to.
FWIW, Racket (a Scheme descendant) has always had an traditional object system library (ordinary class-instance, plus mixins), which was used for its cross-platform GUI library and IDE application framewok, but is not often used for other things. Racket's somewhat simpler `struct`, however, is used heavily, for all sorts of things, as are the traditional basic Scheme/Lisp types. Scheme/Racket procedures are also used heavily, including to do things that you'd use objects for in some other languages. And Scheme (and especially Racket) also gives you very powerful tools for syntax extension, which, among its uses, can do things elegantly that would be pretty messy or nonviable to do with traditional OOP classes. You can also roll your own object library in Scheme/Racket -- I once quickly whipped up a simple prototype-delegation model as an extension to portable Scheme, as an exercise, and this is within the abilities of any fairly new Scheme programmer.
(I'm not disrespecting OO. I'm a long-time OO person, in several languages, including having been a commercial developer of fancy OO developer tools, and tend to architect (at least) data of systems with OO or entity-relationship. But, programming-wise lately, I've mostly been working mostly with what used to be considered non-OO "paradigms".)
OOP tends to encourage going through more layers of indirection than necessary or helpful, which makes the flow hard to follow. E.g. instead of just calling a library method, OOP frameworks might have you extending a framework class. This makes OOP code difficult to debug and maintain.
I'm with you in that I never really found the OOP style to mesh well with my brain. I always felt like I had to fight it and dealing with massive chains of class inheritance can make it really hard to reason about and change code. I've spent years building web apps with Python and Ruby too.
Having only spent a short while with Elixir, I'm finding semi-complicated topics and patterns to be a lot easier to digest even with no formal functional programming background.
Like just today I watched a talk where a guy live coded a web framework DSL in about 30 minutes[0]. One with a beautiful API for controllers and a router. It was a total head explode moment for me (but not in a bad way, it was like seeing the light).
I'm still a newbie with functional programming but so far it feels much more like a WYSIWYG style of programming where your domain is front and center where with OOP it feels like you're bombarded with purposely confusing legal documents and fine print with a small bit of your domain thrown into the mix.
[0]: https://www.youtube.com/watch?v=GRXc-jKRESA
But in its traditional Gang of Four incarnation (which I encountered years later as a newbie), it felt like an awful lot of ceremony and unneeded complexity to get anything done.
I've known most of these folks since the '80s and something that we've discussed a few times is wondering if it's when we got serious about computers, what platform (e.g. C64 or Apple II), or continuous employment in the field (I have a history of going off to do other things most of my dev friends have always been devs), preferred platform today (mostly WinTel vs. Linux), or the niche we work in (I mostly work in embedded stuff, lots of my friends work with stuff related to pretty big systems, either big back end stuff or user facing I/O).
Anyway I find the recent popularity of alternative styles to a welcome recurring innovation, especially the idea of having a "Functional Core with an Imperative Shell".
Also because mutable object state is problematic--it makes a large system harder to reason about and certan types of bugs more likely, and makes thread safety very difficult.
It is possible to build these systems using OO, by reifying the network of functions and the data into objects. But all you are really doing is reinventing functional programming with a layer of OO cruft on top of it, and it is always going to be easier to build the equivalent system in a language that lets the functions and the data structures be unencumbered.
For programming tasks that actually are best conceptualised as objects, OO works quite well. These usually involve the provisioning of some largely static virtual environment, such as a UI, game world, or inversion of control container. Here, objects are long-lived, genuinely independent, and interact in well-defined ways via actions and events, usually swapping only small pieces of state.
My guess is that the advent of GUIs and sophisticated games in the 80s and 90s pushed programmers to be more interested in this latter type of problem, and this was reflected in the languages that evolved. Then the advent of the internet and machine learning in the 2000s and 2010s revived interest in data processing and data flow, and so language design began to shift back.
I suspect in 100 years, if humans are still doing any programming, all popular languages will be mixed-paradigm, and the question of OO vs functional will simply be a blend decided by needs of the problem at hand, rather than the kind of dogmatic arguments of this era, which will be viewed through a historical lens as rather quaint and silly.
What makes you think we'll need to wait that long? C#, C++, JavaScript, Java, Swift, Kotlin, Dart, Scala, Python, and Ruby all have objects, methods, polymorphism, first-class functions, lambdas, closures, and higher-order functions.
This may sometimes lead to code that is difficult to reason out... but if you have a few objects and many functions, OOP can be less verbose than FP.
I say this as a stronger supporter of FP than OOP.
Actually data oriented design and entity component systems work much better for games. You don't have hidden states and data flows much nicer from system to system. Actually, ECS is a good solution for implementing GUIs, too.
OOP is fine, and it makes sense for lots of things. But going crazy with OOP makes for a mess, and a lot of software and languages and tools have chosen "let's do OOP" over "let's do good software". So non OOP stuff is just the natural backlash to OOP gone wild.
I like the idea of encapsulating functions with data and like the ability to inherit to subclasses. I don't really like languages like java that enforce oop at the language level. I prefer languages like D, python or c++ that offer it as a tool but don't restrict you to writing strictly oop code. I find D's class implentation to be the simplest to understand. With classes being reference types with inheritence and structs being value types lacking inheritence. It makes it clear to me when a type should be a class and when it should be a struct or some other data type.
I find complex business logic tends to be best modelled using algebraic data types and functional programming.
But I will assume you're a better observer than me, and make the following claims:
I think there's two reasons: Object-oriented style inheritance is unsound (inheritance is not subtyping), so academics don't like it. Moreover, classical OO is not composable or extensible - unless you write your own primitives in every application and end up with Java-like verbosity. Therefore, research in new features tend not to be object oriented. Therefore, new languages tend to adopt non-OOP styles.
The other reason is probably that there's enough OOP languages. The effort it takes to create a new OOP language exceeds the effort it takes to shoehorn your OOP algorithm into an existing OOP language. Therefore, there's less motivation from the engineering side.
On the other hand, there's still lots of scope for better functional style languages; that marketplace isn't exhausted yet. Rust, for instance, merges a lot of academic techniques trialled on functional languages with a systems programming architecture.
Most modern OO type systems rule out unsound inheritance. (Java, Scala, etc.)
I think we need languages that bring new paradigms. All that you quoted fail in this respect.
https://www.youtube.com/watch?v=0fpDlAEQio4
What's an "object" anyway? A collection of data fields bound to a method is what most people would probably consider. In which case, both golang and Rust have objects.
Nim isn't object oriented either, it's procedural oriented just like C. Experienced Nim devs avoid methods and inheritance and only use them after very careful considerations.
Mutation is needed to implement low-level stuff and have deterministic memory usage.
I mean, it took over programming like a revolution, like structured programming, but structured->OO doesn't have anything nearly like the benefits of unstructured->structured. It seems like a good paradigm for a couple of problem domains, but IMHO, it got cargo-culted on to everything about mainstream programming without delivering much on the promises it got sold with in regard to increased productivity and code re-use.
Just look at the functional components and hooks in react.
They have just abandoned the class based syntax.