I am a recovering object oriented programmer. For years I drank the OOP kool aid and I used to think in terms of classes, objects and design patterns and I sang the "everything is an object" mantra. What changed my mind was two things: type classes (in Haskell) and duck typing (in Python and JavaScript). Now my (unpopular) opinion is that object oriented programming is just a passing fad in programming, but it's one that has gone on for too long and done immense damage to the brains of programmers as well as software engineering in general.

A major theoretical issue with object oriented programming is that it's not based on any theory, but it's a ad hoc construction without any proofs or axioms that can be reasoned about. There are some loose principles like the Liskov Substitution Principle, but they're not really any kind of formal theory.

As I was writing OO code, I started noticing a pattern. The first was that a vast majority of the classes I wrote were practically a boilerplate-heavy way of doing partial application of functions. The second was that I almost never used plain vanilla inheritance (Cat extends Mammal extends Animal) but almost every time I used inheritance, it was only to build some design pattern used to fit a square peg into a round hole. The rest of the cases where I used inheritance were mostly implementing some kind of sum types or algebraic data types.

In dynamically typed languages, single dispatch method calls and inheritance adds very little to the language. JavaScript works just fine without classes or inheritance. Extending or composing objects works simply by adding or overwriting object members. Multiple dispatch methods (in e.g. Python) use the type of an object as metadata to select the correct implementation, but using type information directly is usually frowned upon as it's a bad fit with duck typing. It's way easier to just add the required metadata to the object and not rely on run-time typing information, and it's still prettier than using Visitor pattern for multiple dispatch. (Clojure's multiple dispatch is pretty sweet btw)

In statically typed languages, the problem is that most popular programming languages have a really crappy typing system. C-style typing is very limited but for some reason the typing system in most popular programming languages (Java, C#) is based on C's type system with a virtual function dispatch table bolted on. Add a little syntactic sugar and you've got OOP.

There's a whole world out there in more powerful and interesting typing systems. Haskell has a very practical typing system, with algebraic data types / sum types for "static polymorphism" (A finite amount of types, somewhat equivalent to the limits of Visitor pattern) and type classes for "dynamic polymorphism" where new types (that implement the same interface) may be added after the initial definition.

Basically any OOP pattern can be pretty easily reproduced in Haskell's typing discipline. However, that is often not the best solution since there are more powerful tools available. E.g. compare a factory pattern to Haskell's Read class.

Once I unlearned OOP, I felt liberated from a bunch of stupid restrictions and ideologies that should dictate what my code looks like. Programming is fun again.