There are definitely cases where you could have a module with distinct responsibilities; so you can definitely get high cohesion and loose coupling without OOP, that's true, but there are cases where you may want to:
- Control the timing of when a module is activated (instantiated).
- Have multiple instances of a module with variations in functionality where those variations are not a concern to the parent module/instance.
For me, this is when OOP becomes most useful. If I can write some code once and later use it to create any number of independent instances with the same functionality which can clean up after themselves, this is generally a lot more maintainable than having one module to keep track of all the different states in an array and micromanaging (for example) the rendering and cleanup work associated with multiple distinct pieces of state.
To be clear I'm not arguing against objects here, I don't disagree with anything you wrote. I'm only arguing against the somewhat commonly held idea that OOP helps with loose coupling.
Interfaces (a feature of OOP) can be used to decrease coupling, but the paradigm itself doesn't really provide any assistance in regards to coupling.
The default is for objects to directly instantiate other concrete objects and to be able to directly communicate with any object they at any point come in contact with. That is strong coupling, by default.
You have to do extra work to get looser coupling. Smalltalk style OO is much better (regarding coupling) because you don't have explicit interfaces, you can always replace an object with another. You can also query all live objects of a certain type and replace all of them with a proxy if you so wish. Way better.
But you can go even further in regards to loosening coupling. You can have objects communicate through a tuplespace (Linda), you can have objects receive and send messages only through ports, you can have objects send a message to their implicit parent who is then responsible for redirecting it.
Mainstream OOP is a very poor paradigm full of issues that gets mogged by everything. Composition-over-inheritance is an admission of defeat for a paradigm that lacks native delegation support (good on Kotlin for realising this).
It even gets mogged by languages like Haskell that introduced the more loosely coupled idea of typeclasses, which were inherited by Rust (traits), Swift (protocols), Go (interfaces, not to be confused with declaration-time interfaces as in Java).
We dug a suboptimal hole and stuck our head in there for 50 years.