OOP and ownership are two concepts that mix poorly - ownership in the presence of OOP-like constructs is never simple.
The reason for that is OOP tends to favor constructs where each objects holds references to other objects, creating whole graphs, its not uncommon that from a single object, hundreds of others can be traversed.
Even something so simple as calling a member function from a member function becomes incredibly difficult to handle.
Tbh - this is with good reason, one of the biggest flaws of OOP is that if x.foo() calls x.bar() in the middle, x.bar() can clobber a lot of local state, and result in code that's very difficult to reason about, both for the compiler and the programmer.
And it's a simple case, OOP offers tons of tools to make the programmers job even more difficult - virtual methods, object chains with callbacks, etc. It's just not a clean programming style.
Edit: Just to make it clear, I am not pointing out these problems, to sell you or even imply that I have the solution. I'm not saying programming style X is better.
I work at a D company. We tend to use OOP only for state owners with strict dependencies, so it's rare to even get cycles. It is extremely useful for modeling application state. However, all the domain data is described by immutable values and objects are accessed via parameters as much as fields.
When commandline apps were everywhere, people dreamed of graphical interfaces. Burdened by having to also do jobs that it was bad at, the commandline got a bad reputation. It took the dominance of the desktop for commandline apps to find their niche.
In a similar way, OOP is cursed by its popularity. It has to become part of a mixed diet so that people can put it where it has advantages, and it does have advantages.
On the flipside, with OOP is usually quite easy to put a debugger breakpoint on a particular line and see the full picture of what the program is doing.
In diehard FP (e.g. Haskell) it's hard to even place a breakpoint, let alone see the complete state. In many cases, where implementing a piece of logic without carrying a lot of state is impossible, functional programming can also become very confusing. This is especially true when introducing certain theoretical concepts that facilitate working with IO and state, such as Monad Transformers.
That is true, but on the flip-flip side, while procedural or FP programs are usually easy to run piecewise, with OOP, you have to run the entire app, and navigate to the statement in question to be even able to debug it.
Imho, most FP languages have very serious human-interface issues.
It's no accident that C likes statements (and not too complex ones at that). You can read and parse a statement atomically, which makes the code much easier to read.
In contrast, FP tends to be very, very dense, or even worse, have a density that's super inconsistent.
> In contrast, FP tends to be very, very dense, or even worse, have a density that's super inconsistent.
Depends on the FP. Pipes make things fairly legible. Example from Elixir:
def some_function(thing, doodad, doohickey) do
thing
|> foo(doodad)
|> bar()
|> baz(doohickey)
|> quux()
end
Also easy to debug -- break on any of those lines, or insert `|> IO.inspect()` at any point for good old fashioned print debugging.
Conversely, the non-FP non-pipe version of this would:
(a) be monstrous and difficult to segment: `quux(baz(bar(foo(thing, doodad)), doohickey))`,
(b) needlessly require OOP: `thing.swizzle(doodad).razzle().blorple(doohickey).dazzle()`, where the functions are methods defined on the preceding construct (or some parent construct, to god-knows-what generation), or
(c) require a lot of throw away variables (you get the picture).
I wish more languages had a pipe construct. It's very handy.
Interestingly, D is one of the few languages to have uniform function call syntax,[0] which makes the dot syntax effectively act as a pipe operator, requiring no OOP coupling for its use. Very neat!
>one of the biggest flaws of OOP is that if x.foo() calls x.bar() in the middle, x.bar() can clobber a lot of local state, and result in code that's very difficult to reason about
That's more a problem of having mutable references, you'd have the same problem in a procedural language.
Very few OO languages track reference mutability with any level of rigor. In C/C++ most devs don't even know what restrict is or how to write code using it correctly (which is very difficult an bug prone), const is unfortunately not enough.
In fact I don't even know of any production language that handles variable mutability with any rigor other than Rust.
It worked alright for Rust, and yes Rust does support OOP, there are many meanings to what is OOP from CS point of view.
I have ported Ray Tracing in One Weekend into Rust, while keeping the same OOP design from the tutorial, and affine types were not an impediment to interfaces, polymorphism and dynamic dispatch.
I don't think it worked well for Rust - in fact one of the core issues of Rust imo, is that it somewhat encourages this OOP style which can cause major headaches when you design an app in a traditional OO way - object compostion, complex and stateful, non-copyable objects full of 'smart' behavior, necessitating clones, and state that needs to be reconciled.
The whole concept of OOP is a major conceptual regression in how it treats aliasing, which is a major headache for compiler writers, necessitating either whole program analysis or JIT like techniques.
The reason for that is OOP tends to favor constructs where each objects holds references to other objects, creating whole graphs, its not uncommon that from a single object, hundreds of others can be traversed.
Even something so simple as calling a member function from a member function becomes incredibly difficult to handle.
Tbh - this is with good reason, one of the biggest flaws of OOP is that if x.foo() calls x.bar() in the middle, x.bar() can clobber a lot of local state, and result in code that's very difficult to reason about, both for the compiler and the programmer.
And it's a simple case, OOP offers tons of tools to make the programmers job even more difficult - virtual methods, object chains with callbacks, etc. It's just not a clean programming style.
Edit: Just to make it clear, I am not pointing out these problems, to sell you or even imply that I have the solution. I'm not saying programming style X is better.