What you are saying certainly applies neatly to getters and setters, but consider that classes are used for far more complex use cases than as a simple data holder. Indeed, if all you need is to store two integer components, you might not need a class.
Of course, in Smalltalk and it's ilk, you only have objects. So there is no opportunity to access data from within a structure (externally) without passing messages.
Dynamic dispatchinga are not necessarily evil, but that also doesn’t exclude C. Not sure how this idea C++ is inherently more dynamic or static comes from. For a ling time you can implement all the dynamic behavior of C++ in C, and it took C++ ‘s template as expressive as C macros.
Sometimes, less is more.that’s where C++ never gets right.
The best paradigm in programming has to be procedural style. The cleanest code I’ve written and read has always been procedural. I even maintain a monster VBA Word Macro at work and - after some much needed refactoring - its pretty easy to understand (ignoring VBA’s warts).
The flow of logic and dependencies on data is much easier to follow in procedural.
Functional stresses composition so functions should stay small, and ADTs allow for all effects (errors!) to be represented. Strong typing helps reduce the number of tests I need to write, as well.
I am so much used to the idea of swapping out functionality (set of functions and a state). For example, just recently I had to swap out an Excel spreadsheet reader/writer library in Go for performance reasons, and would have bled a lot (due to refactoring) had it not been encapsulated as an interface.
If you want to do generic programming, it's much better to write f(a, b) instead of a.f(b).
Suppose I'm writing a generic algorithm that needs some function distance(A a, B b) for generic A and B. Distance is probably a commutative function, why would a.distance(b) be better than b.distance(a)? It's arbitrary.
Secondly, if functions are first-class citizens, and I don't "own" the types A and B, it's much easier to implement distance(A a, B b) myself than it is to extend the types A and B with a member function `distance`.
OOP is just functions that are only polymorphic in 1 argument, when you want it on all of them.
Effective C++ (by Scott Meyers) Item 23:
“Prefer non-member non-friend functions to member functions. Doing so increases encapsulation, packaging flexibility, and functional extensibility.”
The text has a lot more detail but that’s a brief summary. Folks just need to read the literature then this sort of knowledge would be in common use.
One downside of language evolution is a lot of people focusing on new language features, etc, but then some of this older, important knowledge gets skipped over.
The biggest benefit is the structure, you get an API that can control any aspect of the app out of the box without the need to redeclare stuff.
Interfaces are much better. What's the difference? It's this: no inheritance.
Still, with interfaces your complaints remain unsatisfied. But add generic functions and then you can have the mix of OO-like and not-OO-like APIs you have in mind.
Composition? AssignShiftLeftExpression has-a AssignOpExpression which has-a BinaryAssignExpression which has-a BinaryExpression which has-a UnaryExpression which has-a Expression which has-a Node?
return this.assignOpExpression.binaryAssignExpression.binaryExpression.unaryExpression.expression.node.op;
or return this.op package hello
type cat int
func greet(c cat) cat {
return c + 1
}
type dog string
func greet(d dog) dog { // greet redeclared in this block
return d + "one"
}
but you can with methods: package hello
type cat int
func (c cat) greet() cat {
return c + 1
}
type dog string
func (d dog) greet() dog {
return d + "one"
}But if you insist, it would look something like:
typedef ... cat;
typedef ... dog;
void cat_greet(cat *obj) {/*do stuff */}
void dog_greet(dog *obj) {/*do stuff */}
#define greet(X) _Generic((X), cat: cat_greet, dog: dog_greet)(X)
int main(...)
{
cat mycat;
dog mydog;
greet(&mycat);
greet(&mydog);
return 0;
} type
Cat = object # these are just 'struct' in a different skin
Dog = object
proc greet(c: Cat): string = "Meow"
proc greet(d: Dog): string = "Bark"
assert Dog().greet() == "Bark"
assert Cat().greet() == "Meow"I recently watched a talk on just what precisely Functional Programming is, and the whole time I just thought "wait, doesn't everyone do this? Isn't this just the overwhelmingly obvious way to write code?" Around the same time, I found myself running into some very OO projects, and simply couldn't understand the actual reason for all of the ceremony and ridiculous amounts of encapsulation. And this wasn't in Java, which of course is infamous for its seemingly infinite amount of ceremony -- this was the documentation of a widely-used GUI library for Python.
Like, to my mind, classes are useful when you need to create objects whose values are unknown, i.e. the analogy where the class is a "cookie cutter" which creates the cookies. Classes are perfectly fine for that use-case but by no means should it be that which forms the backbone of your project; my (admittedly limited) view is that stand-alone functions are far and away the lowest-overhead and reliable device for composing parts of a system.
I really get the feeling that OOP is sort of like how synths were in the 80s -- everyone just kinda lost their minds for a second and used them everywhere, whether it made sense for the music or not. But from what I can tell, OOP just isn't really the right paradigm for a whole bunch of projects, and often the design of the code vs the design of the thing feels like ramming a square peg into a round hole.
Edit: all that said, if the above smacks of the Dunning-Krueger effect, I'd love to be made aware of that.
It's the small things in programming that can make a huge difference. It always felt like I should be using the C++ way because it was slightly dry-er and looked nicer, and I could have all these OO features.
Like when you see `int get_numer(void *r)` or Python's `def get_numer(self)`, or Go's `func (rational *Rational) GetNumer() (n int)`, you think: "how silly, why does the object pointer need to be in scope, just use `this`". But this tiny thing is liberating. It allows you to see that everything is just functions operating on data. And that a "method", is just a function that is taking _the entire object_ as a parameter...and hence a dependency. Which allows you to think: hmm, does this function really need to depend on the entire object...maybe it can be a separate utility function all by itself without any connection to the class. And maybe it doesn't actually need access to any of the other functions in the class...and maybe the class could be split up...etc.
I just watched a [talk](1) by Alan Kay the inventor of SmallTalk and the phrase "object-oriented" who never stops shitting on C++.
Yet OO is still absolutely everywhere.
[1]: https://www.youtube.com/watch?v=oKg1hTOQXoY