* The ML guys can use OCaml, where the O is Objective. Ocaml contains a concept of OOP built into the language already (namely structural subtyping - someone could call this typed duck-typing). The ML guys in general also have another tool for abstraction at their disposal: ML-style functors.
* Erlang programmers often use processes which are self-contained concurrently executing "objects". In Erlang a process is lightweight enough to be considered a "heavy" object in OOP so you often structure your programs around several processes messaging each other to carry out solutions to tasks.
Different languages provides different tools as a means of abstraction. It is all just about unearthing the alternative.
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.