> The industry and the academy have used the term “object-oriented” to mean so many different things.
I think we can safely stick to how IEEE defines OOP: the combination of three main features: 1) encapsulation of data and code 2) inheritance and late binding 3) dynamic object generation (from https://ethw.org/Milestones:Object-Oriented_Programming,_196...).
The article assumes that C++, Java, and Smalltalk implement completely different subsets of OOP features, which is not true at all. Those languages, including Smalltalk (starting with Smalltalk-76), all implement the Simula 67 object model with classes, inheritance and virtual method dispatch. Simula 67 was the first object-oriented programming language (even if the term was only applied ~10 years later in a 1976 MIT publication for the first time, see https://news.ycombinator.com/item?id=36879311). Message passing (the feature the article claims is unique to Smalltalk) is mathematically isomorphic to virtual method dispatch; and also Smalltalk uses method dispatch tables, very similar to C++ and Java.
Even before I got to the point where I decided I didn't like inheritance, I distinctly recall having conversations about how I felt that using inheritance for anything other than polymorphism didn't usually end up with particularly clean code. I can remember a conversation about this at least as far back as the summer after my freshman year of college, and I don't think I was aware of the idea of "composition" yet, because I remember phrasing my point as something like "inheritance shouldn't be used for 'code-sharing', only for polymorphism".
Out of curiosity, when you say you don't like inheritance, does that mean you never use it at all, or you only use it rarely?
Because even though inheritance often is used in a wrong way, there are definitely cases, where it is the clearest pattern in my opinion.
Like graphic libary things. E.g. everything on the screen is a DisplayObject. Simple Textfields and Images inherit directly from DisplayObject. Layoutcontainers inherit from DisplayObjectContainer which inherits from DisplayObject.
Inheritance here makes a lot of sense to me and I don't see how it could be expressed in a different way without loosing that clarity.
The trick is to say "codata" instead of "object-oriented programming", and then you can use OOP and still be a programming hipster. (I am a programming hipster.)
I'm only somewhat joking. I actually find this view very useful. Codata is basically programming to interfaces, which we can think of as OO without confusing implementation approaches like inheritance. Codata is the dual to (algebraic) data, meaning we can convert one to the other. We can think of working with an abstract API, which we realise as codata or data depending on what best suits the project. More in the book I'm writing [1].
In general I agree with the author. There are a lot of concepts tangled up in OOP and discussion around the benefits of OOP are rarely productive.
For me, the fundamental gestalt was/is binding behavior to data. I found/find it useful for modeling how things work. I was always inspired by one of Alan’s seed inspiration for Smalltalk, how biological cells accomplish computation through interacting with each other. I did Smalltalk for many years before polyglotting.
Regarding Message Passing and Late-binding, I think it's important to take into account that Alan Kay was working on Smalltalk -- a system that was image based; a system where you could change things as it was running. I think that message passing and late-binding are often championed but then sort of fall flat given standard deployment techniques: build & deploy (often to a ephemeral runtime / container).
Smalltalk can use build & deploy. (Image as cache, not archive.)
"At the outset of a project involving two or more programmers: Do assign a member of the team to be the version manager. … The responsibilities of the version manager consist of collecting and cataloging code files submitted by all members of the team, periodically building a new system image incorporating all submitted code files, and releasing the image for use by the team. The version manager stores the current release and all code files for that release in a central place, allowing team members read access, and disallowing write access for anyone except the version manager."
1984 "Smalltalk-80 The Interactive Programming Environment" page 500
Yes, you could also build and deploy a Smalltalk system, but my point is that the “build & deploy” approach (to me) seems antithetical to the message passing and late-binding paradigms. To use another example, it seems like you lose a lot of the benefits of Common Lisp via Slime (hot code reloading) if you deploy your Common Lisp app to a short-lived, ephemeral environment.
> I feel that prototypes are harder to wrap one’s head around compared to classes.
This is sad to read because prototypes are conceptually easier to understand than classes. It’s unfortunate that most developers experience with them is JavaScript, because its implementation is extremely poor. I recommend trying Io which is very Self inspired as well as Lua.
"The notion of an interface is what truly characterizes objects - not classes, not inheritance, not mutable state. Read William Cook's classic essay for a deep discussion on this."
There's this quote from Robert C Martin (Uncle Bob)
> Structured Programming imposes discipline on direct transfer of control. Object Oriented Programming imposes discipline on indirect transfer of control. Functional programming imposes discipline upon assignment. Each of these paradigms took something away. None of them added any new capability. Each increased discipline and decreased capability.
The interface (or extensible class) enables safe indirect transfer of control.
I think the biggest mistake was to teach inheritance as a main feature of OOP. I have done some stuff with inheritance but it was very specialized and it would have been fine without inheritance.
My OO projects were usually in Java with a DB. They all ran afoul of what Martin Fowler calls the Anemic Domain Model. Basically your objects are data-only, so there's no benefit. In addition Spring injection became ubiquitous, and further killed objects with behavior. The only project using a DB and had objects with behavior was an old one that happened to use TopLink as an OR mapping.
> Basically your objects are data-only, so there's no benefit.
This makes me wonder why most of us use Java at all. In your typical web app project, classes just feel like either:
1) Data structures. This I suspect is a result of ORM's not really being ORM's but actually "Structural Relational Mappers".
- or -
2) Namespaces to dump functions. These are your run-of-the-mill "utils" classes or "service" classes, etc.
The more I work in Java, the more I feel friction between the language, its identity(OO beginning to incorporate functional ideas), and how people write in it.
Java was the first popular language to push static analysis for correctness. It was the "if it compiles, it runs" language of its day, what meant that managers could hire a couple of bad developers by mistake and it wouldn't destroy the entire team's productivity.
I'm not sure that position lasted for even 5 years. But it had a very unique and relevant value proposition at the time.
A lot of that is down to how people rely on frameworks that force them into "convenient" abstractions. Like Spring and Hibernate. But those are not the language. They represent a (vocal) subset of programmers.
You don't need an ORM or an overgrown dependency injection framework to create a webapp in Java.
Service classes are the thing I hate most. They’re just namespaces for functions. They’re a product of Java not being able to have top level functions.
Not everything can be associated to a single entity. Many operations work on two or more entities where none of them are the "master". Otherwise you end up with "coordinators".
Users.create(...) is the easy case. Try transfer_permissions(user1, user2, entity) while retaining transactionality and the ability of either user to cancel the transfer.
I'm not sure why having a global function by the same would make this any easier or harder to implement. But it would pollute the global namespace with highly specific operations.
Java is a waste of time for the reasons you said. People use it for legacy reasons. Back then, the alternatives like JS just weren't there yet in several spaces like backend. Your alternatives were even more cumbersome like C++.
OO fatigue is a healthy symptom of readiness to move to clojure, where data and functions are free to live without encapsulation. No king of nouns, no king of execution!
There is a good methodological principle stated by a Soviet philosoph Ilyenkov: to understand the nature of a thing build or describe a minimal working model of the thing. So simple that if you remove any single detail it ceases to work. He himself gave an example of radio: to understand what radio is build a minimal radio sender and receiver of three or four details each. Do not start with a household radio unit that comes in a polished box with lights and knobs; it has way too many unrelated details.
This works very well both for concrete things like radio and for more abstract things like math or Marx's notion of private property. This is also the principle employed by religious and mystical parables or the book "A pattern language".
As beautifully shown in that paper, all you need is a primitive “message send” operation and a “lookup” message, pretty much everything else in OOP isn’t necessary or can be implemented at run-time.
OOP basically means Java or things like Java. Not how it started ofc, but that's what it's been for decades. Minus the lambdas and stuff they added relatively later to compromise on OOP.
I'd love a deeper dive on how Objects work in NeXTSTEP. From their brochures, they talk about objects being persistent and distributable, that in a workgroup of people everyone can rely on the objects being up to date.
I've always been so curious what the broader technical ecosystem looks like here. Presumably there are still processes running on systems. But these processes have lots of objects in them? And the objects are using Mach message passing to converse with other processes elsewhere? Within an application, are objects communicating across Mach too?
This is a fun work. It feels like the brief outline for a Speaking for the Dead for OOP. Huge amount of things to lots of different people over time.
Seconding @rawgabbit's recommendation for Casey Muratori's The Big OOPs: Anatomy of a Thirty-five-year Mistake, which really is hunting back and back for the cosmogenesis of objects, and covers so much terrain. Objectogenesis?
https://youtu.be/wo84LFzx5nI
On classes, I get it... tbf though I'm fine with prototype inheritance as well, there's positives and negatives to both approaches... not to mention, there are benefits to not really having either and just having objects you can interrogate or even that are statically assigned at creation (structs).
What's funny on the Method Syntax for me, is that I actually don't like mixing classes that hold data and classes that do things more often than not. I mean, I get the concepts, but I just don't generally like the approach. The only exception might be a controller with a handle to a model(state) and the view... but even then, the data itself (model) is kind of separated as a reference, and don't tend to attach too many variants of state to anything... I'm generally a fan of the single state tree approach (often used for games, and famously via Redux).
On information hiding... I'm generally not too much of a fan of hiding members of an object used to hold data... I mean, I can see filters when you're passing something to the edge of a system, like a hashed password on a user object exposed via an api. But internally, I'd almost rather see immutability as a first class over locking bits and pieces down, then exposing member methods to mutate the object internally. Just my own take.
On Encapsulation, like above... I'm more on the side of the Data oriented design approach. To me this is where you have API surfaces and like above I tend to separate modules/classes that do things, from templates/models/classes that hold data.
I'm mixed on Interfaces.. they're definitely useful for plugin systems or when you have multiple distinct implementations of a thing... but after a couple decades of C#, they're definitely overrated and overused.
No strong opinions on Late Binding pr Dynamic Dispatch... other than I do appreciate it at times in dynamic language environments (JS).
Inheritance and SubTyping imo are, similar to Interfaces, somewhat overrated... I just try to avoid them more than use them. There are exceptions, I'm actively using this in a project right now, but more often than not, it just adds undue complexity. With prototype based inheritance, it's also possible to really slow down certain processes unintentionally.
Strong proponent of Message Passing approaches... it often simplifies a solution in terms of the surface you need to be aware of at a given point. Allows you to construct decision trees and pipelines of simpler functions.
Interesting overall... but still not a fan of some of the excesses in OOP usage in practice that I've had to deal with. I just prefer to break problems up slightly differently... sometimes blurring clear lines of separation to have a simpler whole, sometimes just drawing the lines differently because they make more sense to me to break up for a given use case.
HN doesn't allow image replies, but if you were to image search "smug meme" you would find any of them to be an appropriate response to this useless post of yours, and imagine I put it here for your convenience
> The industry and the academy have used the term “object-oriented” to mean so many different things.
I think we can safely stick to how IEEE defines OOP: the combination of three main features: 1) encapsulation of data and code 2) inheritance and late binding 3) dynamic object generation (from https://ethw.org/Milestones:Object-Oriented_Programming,_196...).
The article assumes that C++, Java, and Smalltalk implement completely different subsets of OOP features, which is not true at all. Those languages, including Smalltalk (starting with Smalltalk-76), all implement the Simula 67 object model with classes, inheritance and virtual method dispatch. Simula 67 was the first object-oriented programming language (even if the term was only applied ~10 years later in a 1976 MIT publication for the first time, see https://news.ycombinator.com/item?id=36879311). Message passing (the feature the article claims is unique to Smalltalk) is mathematically isomorphic to virtual method dispatch; and also Smalltalk uses method dispatch tables, very similar to C++ and Java.
Inheritance is just the unnecessary coupling of composition and polymorphism.
Even before I got to the point where I decided I didn't like inheritance, I distinctly recall having conversations about how I felt that using inheritance for anything other than polymorphism didn't usually end up with particularly clean code. I can remember a conversation about this at least as far back as the summer after my freshman year of college, and I don't think I was aware of the idea of "composition" yet, because I remember phrasing my point as something like "inheritance shouldn't be used for 'code-sharing', only for polymorphism".
Out of curiosity, when you say you don't like inheritance, does that mean you never use it at all, or you only use it rarely?
Because even though inheritance often is used in a wrong way, there are definitely cases, where it is the clearest pattern in my opinion.
Like graphic libary things. E.g. everything on the screen is a DisplayObject. Simple Textfields and Images inherit directly from DisplayObject. Layoutcontainers inherit from DisplayObjectContainer which inherits from DisplayObject.
Inheritance here makes a lot of sense to me and I don't see how it could be expressed in a different way without loosing that clarity.
Delegation is a very useful part of composition. Almost all OOP languages have two techniques to delegate some methods to another object:
- manually write a bunch of forwarding methods and remember to keep them updated, or
- inheritance.
There’s also automatic delegation like in Kotlin: https://kotlinlang.org/docs/delegation.html
Object orientism is just encapsulation. It’s the only thing that is required. You can have objects without inheritance and virtual dispatch.
So Python is not OOP language? You can't hide fields.
You can hide fields in Python with a little bit of gymnastics:
It has conventions to hide data. Good enough.
Python was a mistake if you ask me ¯\_(ツ)_/¯
The trick is to say "codata" instead of "object-oriented programming", and then you can use OOP and still be a programming hipster. (I am a programming hipster.)
I'm only somewhat joking. I actually find this view very useful. Codata is basically programming to interfaces, which we can think of as OO without confusing implementation approaches like inheritance. Codata is the dual to (algebraic) data, meaning we can convert one to the other. We can think of working with an abstract API, which we realise as codata or data depending on what best suits the project. More in the book I'm writing [1].
In general I agree with the author. There are a lot of concepts tangled up in OOP and discussion around the benefits of OOP are rarely productive.
[1]: https://scalawithcats.com/
For me, the fundamental gestalt was/is binding behavior to data. I found/find it useful for modeling how things work. I was always inspired by one of Alan’s seed inspiration for Smalltalk, how biological cells accomplish computation through interacting with each other. I did Smalltalk for many years before polyglotting.
Muratori traced the history of OOP to the original documents. Skip to the 1:18 mark if you want to skip to his findings.
https://youtu.be/wo84LFzx5nI
This is such a good video. I really like the way he presents it as well.
His rant about CS historians is also a fun subject
Regarding Message Passing and Late-binding, I think it's important to take into account that Alan Kay was working on Smalltalk -- a system that was image based; a system where you could change things as it was running. I think that message passing and late-binding are often championed but then sort of fall flat given standard deployment techniques: build & deploy (often to a ephemeral runtime / container).
Smalltalk can use build & deploy. (Image as cache, not archive.)
"At the outset of a project involving two or more programmers: Do assign a member of the team to be the version manager. … The responsibilities of the version manager consist of collecting and cataloging code files submitted by all members of the team, periodically building a new system image incorporating all submitted code files, and releasing the image for use by the team. The version manager stores the current release and all code files for that release in a central place, allowing team members read access, and disallowing write access for anyone except the version manager."
1984 "Smalltalk-80 The Interactive Programming Environment" page 500
Yes, you could also build and deploy a Smalltalk system, but my point is that the “build & deploy” approach (to me) seems antithetical to the message passing and late-binding paradigms. To use another example, it seems like you lose a lot of the benefits of Common Lisp via Slime (hot code reloading) if you deploy your Common Lisp app to a short-lived, ephemeral environment.
> (to me) seems antithetical to the message passing and late-binding paradigms
(To me) seems like build & deploy as dev process and message-passing & late-binding as implementation technique.
Separate concerns, I probably misunderstood.
> I feel that prototypes are harder to wrap one’s head around compared to classes.
This is sad to read because prototypes are conceptually easier to understand than classes. It’s unfortunate that most developers experience with them is JavaScript, because its implementation is extremely poor. I recommend trying Io which is very Self inspired as well as Lua.
"The notion of an interface is what truly characterizes objects - not classes, not inheritance, not mutable state. Read William Cook's classic essay for a deep discussion on this."
- Gilad Bracha
https://gbracha.blogspot.com/2022/06/the-prospect-of-executi...
http://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf
There's this quote from Robert C Martin (Uncle Bob)
> Structured Programming imposes discipline on direct transfer of control. Object Oriented Programming imposes discipline on indirect transfer of control. Functional programming imposes discipline upon assignment. Each of these paradigms took something away. None of them added any new capability. Each increased discipline and decreased capability.
The interface (or extensible class) enables safe indirect transfer of control.
I always considered an "object" to be data with identity and state.
All the other stuff, like polymorphism, encapsulation, etc., I consider "addons."
I think the biggest mistake was to teach inheritance as a main feature of OOP. I have done some stuff with inheritance but it was very specialized and it would have been fine without inheritance.
Back in the day, I used to do OOP with C.
It was a common pattern, back then. We’d pass around structs, and have a small library of functions that accessed/modified the data in these structs.
If you wanted, you could add function pointers to the structs. You could add “polymorphism,” by overwriting these pointers, but it was messy.
That said, inheritance can be very useful, in some cases, like improving DRY. I don’t like to take absolute stances, so much, these days.
My OO projects were usually in Java with a DB. They all ran afoul of what Martin Fowler calls the Anemic Domain Model. Basically your objects are data-only, so there's no benefit. In addition Spring injection became ubiquitous, and further killed objects with behavior. The only project using a DB and had objects with behavior was an old one that happened to use TopLink as an OR mapping.
> Basically your objects are data-only, so there's no benefit.
This makes me wonder why most of us use Java at all. In your typical web app project, classes just feel like either:
1) Data structures. This I suspect is a result of ORM's not really being ORM's but actually "Structural Relational Mappers".
- or -
2) Namespaces to dump functions. These are your run-of-the-mill "utils" classes or "service" classes, etc.
The more I work in Java, the more I feel friction between the language, its identity(OO beginning to incorporate functional ideas), and how people write in it.
> why most of us use Java at all
Java was the first popular language to push static analysis for correctness. It was the "if it compiles, it runs" language of its day, what meant that managers could hire a couple of bad developers by mistake and it wouldn't destroy the entire team's productivity.
I'm not sure that position lasted for even 5 years. But it had a very unique and relevant value proposition at the time.
A lot of that is down to how people rely on frameworks that force them into "convenient" abstractions. Like Spring and Hibernate. But those are not the language. They represent a (vocal) subset of programmers.
You don't need an ORM or an overgrown dependency injection framework to create a webapp in Java.
Service classes are the thing I hate most. They’re just namespaces for functions. They’re a product of Java not being able to have top level functions.
Not being able to have top level functions is a feature, not a bug.
You can declare static methods on interfaces in Java, which means you could call things like Users.create("Foobar") if you wanted to.
Not everything can be associated to a single entity. Many operations work on two or more entities where none of them are the "master". Otherwise you end up with "coordinators".
Users.create(...) is the easy case. Try transfer_permissions(user1, user2, entity) while retaining transactionality and the ability of either user to cancel the transfer.
Permissions.transfer(x, y, z)
I'm not sure why having a global function by the same would make this any easier or harder to implement. But it would pollute the global namespace with highly specific operations.
And now you have invented a helper class when all you needed is a namespace. And you already had namespaces.
It is not a feature. Every programming language since has decided this was a mistake.
Can you provide an example of that?
Java is a waste of time for the reasons you said. People use it for legacy reasons. Back then, the alternatives like JS just weren't there yet in several spaces like backend. Your alternatives were even more cumbersome like C++.
> People use it for legacy reasons
This is so incredibly wrong it must be a troll.
Why did you create an anemic domain model?
Java has had "data carriers" in the form of records for a while now. Immutable(ish), low boilerblate, convenient.
Records are great when doing more "data oriented programming".OO fatigue is a healthy symptom of readiness to move to clojure, where data and functions are free to live without encapsulation. No king of nouns, no king of execution!
There is a good methodological principle stated by a Soviet philosoph Ilyenkov: to understand the nature of a thing build or describe a minimal working model of the thing. So simple that if you remove any single detail it ceases to work. He himself gave an example of radio: to understand what radio is build a minimal radio sender and receiver of three or four details each. Do not start with a household radio unit that comes in a polished box with lights and knobs; it has way too many unrelated details.
This works very well both for concrete things like radio and for more abstract things like math or Marx's notion of private property. This is also the principle employed by religious and mystical parables or the book "A pattern language".
Minimal working model of object orientation, that cannot be simplified further? Look no further than Piumarta’s https://piumarta.com/software/id-objmodel/
As beautifully shown in that paper, all you need is a primitive “message send” operation and a “lookup” message, pretty much everything else in OOP isn’t necessary or can be implemented at run-time.
OOP basically means Java or things like Java. Not how it started ofc, but that's what it's been for decades. Minus the lambdas and stuff they added relatively later to compromise on OOP.
People who hate OOP just didn't learn CLOS.
That would be the Common Lisp Object System. And not using undefined abbreviations might help popularise it.
[dead]
I'd love a deeper dive on how Objects work in NeXTSTEP. From their brochures, they talk about objects being persistent and distributable, that in a workgroup of people everyone can rely on the objects being up to date.
I've always been so curious what the broader technical ecosystem looks like here. Presumably there are still processes running on systems. But these processes have lots of objects in them? And the objects are using Mach message passing to converse with other processes elsewhere? Within an application, are objects communicating across Mach too?
There's so much high level rhetoric about. Such as this bit. But I'd love a real technical view at what was happening, what objects really were here. https://computerhistory.org/blog/the-deep-history-of-your-ap... https://news.ycombinator.com/item?id=42111938
This is a fun work. It feels like the brief outline for a Speaking for the Dead for OOP. Huge amount of things to lots of different people over time.
Seconding @rawgabbit's recommendation for Casey Muratori's The Big OOPs: Anatomy of a Thirty-five-year Mistake, which really is hunting back and back for the cosmogenesis of objects, and covers so much terrain. Objectogenesis? https://youtu.be/wo84LFzx5nI
Commenting while reading...
On classes, I get it... tbf though I'm fine with prototype inheritance as well, there's positives and negatives to both approaches... not to mention, there are benefits to not really having either and just having objects you can interrogate or even that are statically assigned at creation (structs).
What's funny on the Method Syntax for me, is that I actually don't like mixing classes that hold data and classes that do things more often than not. I mean, I get the concepts, but I just don't generally like the approach. The only exception might be a controller with a handle to a model(state) and the view... but even then, the data itself (model) is kind of separated as a reference, and don't tend to attach too many variants of state to anything... I'm generally a fan of the single state tree approach (often used for games, and famously via Redux).
On information hiding... I'm generally not too much of a fan of hiding members of an object used to hold data... I mean, I can see filters when you're passing something to the edge of a system, like a hashed password on a user object exposed via an api. But internally, I'd almost rather see immutability as a first class over locking bits and pieces down, then exposing member methods to mutate the object internally. Just my own take.
On Encapsulation, like above... I'm more on the side of the Data oriented design approach. To me this is where you have API surfaces and like above I tend to separate modules/classes that do things, from templates/models/classes that hold data.
I'm mixed on Interfaces.. they're definitely useful for plugin systems or when you have multiple distinct implementations of a thing... but after a couple decades of C#, they're definitely overrated and overused.
No strong opinions on Late Binding pr Dynamic Dispatch... other than I do appreciate it at times in dynamic language environments (JS).
Inheritance and SubTyping imo are, similar to Interfaces, somewhat overrated... I just try to avoid them more than use them. There are exceptions, I'm actively using this in a project right now, but more often than not, it just adds undue complexity. With prototype based inheritance, it's also possible to really slow down certain processes unintentionally.
Strong proponent of Message Passing approaches... it often simplifies a solution in terms of the surface you need to be aware of at a given point. Allows you to construct decision trees and pipelines of simpler functions.
Interesting overall... but still not a fan of some of the excesses in OOP usage in practice that I've had to deal with. I just prefer to break problems up slightly differently... sometimes blurring clear lines of separation to have a simpler whole, sometimes just drawing the lines differently because they make more sense to me to break up for a given use case.
One may argue xml is superior to json, which it is.
But json wins out because it can be learned much more quickly.
Something similar could be said with OOP vs Functional Programming.
So which one is the quickest to learn? I think Python is easy to learn and C++ is hard, and Scheme is easy and Haskell hard.
Python can be somehow consider OOP since evrything inside it are Object, even function and module.
I expected this to be a play on the old joke about Java being designed to appeal to people who were into SM/B&D.
[dead]
[flagged]
HN doesn't allow image replies, but if you were to image search "smug meme" you would find any of them to be an appropriate response to this useless post of yours, and imagine I put it here for your convenience