This remark interests me: "except that OOP languages know to pass this for you".
In practice, i'm not seeing this. In that procedural example, you can pass in any input you'd like. Suppose the function in that example accepts an array. Now, inside the function, I have to devote a non-trivial amount of effort to determine if the array contains the type of data I'm expecting. And if I want that function to change the array for possible use later, I'll need it to return the array and I'll have to store that return.
Now, that array, implemented as an Object, is structured. Strongly typed. Even private properties with setters to enforce consistency.
And that procedure, implemented as a method on that object, needs none of the consistency and validation code at the top of the procedure. And it can make changes to the properties themselves without the need to pass around variables.
Sure, most of the benefit of OO is in the structure and organization it brings. But no, it's far more than, as you're making it sound, the compiler is passing around an implicit argument on your behalf.
I think what you're talking about for type-safety, not necessarily OOP. C supports types, but is not completely type-safe. It will tell the developer that they shouldn't be doing something, but won't enforce it.
The argument above is that most developers write object-oriented code in procedural languages. They have a set of functions that perform actions on a specific type of data structure. To encapsulate functionality and keep the codebase clean, all reads and writes to these data structures should go through these functions. This pattern is ubiquitous in well-written C code. OOP itself simply provides a standard framework and syntactic sugar to make this type of code more consistent and clear.
No, not entirely. A strongly typed language will help this a little -- at least I can know i have, say, an array of strings.
Suppose i have 2 properties in my class, 'start_date' and 'end_date'. They are private and have setters.
In a method that uses those 2 properties, I can trust that they are of the right type, that start_date is < end_date, that they represent a reasonable range based on my logic defined in the setters. All of this can be taken for granted.
In this procedural example, imagine some sort of hash or struct or associative array (whatever you want to call it in your given language) is supplied with those 2 keys.
The procedure cannot trust that the inputs are valid. It has to check everything. If you can rely on type safety, congrats, that's one less check. But there are plenty of others.
So you create a typedef struct of date_range to store the start_date and end_date and use a set of date_range_* functions to manipulate that struct. Most OOP languages allow direct access to even private variables if the developer really wants to, so the guarantees are pretty weak. C++, Java, C#, Ruby, Python... all of those allow indirect access to "private" methods and variables. You must still rely on other developers to do the right thing and not screw things up.
I think the argument that a developer can access and write-to private methods bypassing setters so therefore encapsulation is broken is pretty weak IMO.
On one hand you have an option where you have to trust the developer will chain the appropriate method calls to build an unrelated data structure that gets passed-in to your target method.
On the other, you have the possibility that a developer will knowingly and willing subvert the encapsulation by writing directly to private variables while ignoring the business logic that's being enforced in the setters.
Do you mind me asking... what exactly are you arguing there? In the first example it's easier to do it wrong than it is to do it right. In the second example it's easier to do it right than it is to do it wrong -- in fact you have to specifically write to protected members to break it.
I believe your conclusion is rooted in a lack of experience in writing C code. You seem to think that developers are going to go out of their way to allocate and initialize the struct manually. In almost every codebase I've worked in, struct types always have an allocation/initializer function, so I look for that first, and generally find a set of other functions that are used to work on that struct type, organized into a single .c file usually named something similar to that struct type. It is a convention, not syntactic sugar. In C++ or C#, you have syntactic sugar that makes it more difficult to violate the rule, but does not actually enforce anything.
Sure, most of the benefit of OO is in the structure and organization it brings. But no, it's far more than, as you're making it sound, the compiler is passing around an implicit argument on your behalf.
I agree with his point that the difference between OOP-style C and code written in an object-oriented language is language support. I have read some very OOP-style C, and it is extremely centered around chunks of data (objects.) The idea that code should be organized around data structures goes way back. The terminology in this Fred Brooks quote shows how old the idea is:
Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious.
In object-oriented C, it is even true that most functions are closely associated with a single structure, as if they are methods, though of course it isn't as cut and dried as it is in languages where that concept is rigidly enforced.
Possibly unrelated, but I've also seen procedural C that is verging on OO, showing an interesting state of evolution: pieces of data are implicitly grouped together, in that they always appear together in function signatures, but not grouped into structures. As a trivial example, if a point had x, y, and z coordinates in space, any function needing one of those coordinates would take all three throughout the entire codebase. Instead of
because points were regarded as aggregates that should be passed around together. This made it much simpler to remember function signatures, and it communicated the intent to treat certain chunks of data as aggregates. (I think this technique was used in Fortran before C even existed.) I imagine that from there it was a very short step to structs.
Almost everything in the history of OO language features has boiled down to support for practices that were first developed and used without language support, often in C. If I remember correctly (remember reading, not being there) people attempted to differentiate between public and private data and methods in C (using macros and/or something similar to the the C++ pimpl trick.)
In practice, i'm not seeing this. In that procedural example, you can pass in any input you'd like. Suppose the function in that example accepts an array. Now, inside the function, I have to devote a non-trivial amount of effort to determine if the array contains the type of data I'm expecting. And if I want that function to change the array for possible use later, I'll need it to return the array and I'll have to store that return.
Now, that array, implemented as an Object, is structured. Strongly typed. Even private properties with setters to enforce consistency.
And that procedure, implemented as a method on that object, needs none of the consistency and validation code at the top of the procedure. And it can make changes to the properties themselves without the need to pass around variables.
Sure, most of the benefit of OO is in the structure and organization it brings. But no, it's far more than, as you're making it sound, the compiler is passing around an implicit argument on your behalf.