6 ms·
> 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
by Rochus 11mo ago
> 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,_1961-1967 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 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.
- LukeShu 11mo agoInheritance is just the unnecessary coupling of composition and polymorphism.
- saghm 11mo agoEven 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".
- lukan 11mo agoOut 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.
- ffsm8 11mo ago> I don't see how it could be expressed in a different way without loosing that clarity. What value does the inheritance provide here? Can't you just use a flat interface per usecase without inheritance and it will work simpler with less mental overhead keeping the hierarchy in mind? Explicitly your graphic library sounds should be fine to have the interface DisplayObject which you can then add default implementations on. (That's a form of composition)
- lukan 11mo agoThat would be way, way more verbose for everything. Every display object has a x y width and height for example. And there is basic validating for every object. Now a validate method can be conposited. But variables? Also the validating, there is some base validating every object share (called wih super) and the specific validating (or rendering) is done down in the subclasses. And even for simple things, you can composite a extra object, but then you cannot do a.x = b.x * 2 anymore, but would have to do a.po.x = b.po.x * 2 etc
- saghm 11mo agoTo avoid splitting the discussion by responding directly to your comment above, since I have thoughts about this one as well: I've written Rust professionally since 2019, which doesn't have inheritance, so I don't use it at all. I guess my point is that I don't miss having inheritance as a tool in my everyday coding, and I actively prefer not having it available in Rust. In terms of what you're saying here, the extra verbosity is not really something that either bothers me or is impossible to work around in the context of Rust at least. The standard library in Rust has a trait called `Deref` that lets you automatically delegate method calls without needing to specify the target (which is more than sufficient unless you're trying to emulate multiple inheritance, and I consider not providing support for anything like that a feature rather than a shortcoming). If I were extremely bothered by the need do do `a.po.x` in the example you give, I'd be able to write code like this: struct Point { x: i32, y: i32, } struct ThingWithPoint { po: Point, } impl Deref for ThingWithPoint { type Target = Point; fn deref(&self) -> &Self::Target { &self.po } } fn something(a: &mut ThingWithPoint, b: ThingWithPoint) { a.x = b.x * 2; } Does implementing `Deref` require a bit more code than saying something like `ThingWithPoint: Point` as part of the type definition? Yes (although arguably that has as much to do with how Rust defines methods as part of `impl` blocks outside of the type definition, so defining a method that isn't part of a trait would still be a slightly more verbose, and it's not really something that I particularly have an issue with). Do I find that I'm unhappy with needing to be explicit about this sort of thing rather than having the language provide an extremely terse syntax for inheritance? Absolutely not; the extra syntax convenience is just that, a convenience, and in practice I find it's just as likely to make things more confusing if used too often than it is to make things easier to understand. More to the point, there's absolutely no reason that makes sense to me why the convenience of syntax needs to be coupled with a feature that actually changes the semantics of the type where I want that convenience; as comment I originally replied to stated, inheritance tries to address two very different concerns, and I feel pretty strongly that ends up being more trouble than it's worth compared to just having language features that address them separately.
- pjc50 11mo agoDelegation 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.
- vips7L 11mo agoThere’s also automatic delegation like in Kotlin: https://kotlinlang.org/docs/delegation.html https://kotlinlang.org/docs/delegation.html
- bccdee 11mo agoTo be fair, the compiler generally forces you to keep the forwarding methods updated. It can be irritating, but there's little risk of forgetting in a statically-typed language. Manual forwarding also operates as a forcing function to write small interfaces and to keep different pieces of logic separated in different layers, both of which feel like good design to me. (Though I'm not saying I'd turn my nose up at a terser notation for method forwarding, haha.)
- LukeShu 11mo ago...yes? Hence me saying that 'composition' and 'polymorphism' have often been unnecessarily coupled together in 'inheritance'? Compare: Ruby mixins or Go embedded struct fields.
- o_nate 11mo agoInheritance is not necessary, but then very few programming constructs are absolutely necessary. The question is does it help program clarity or not. I think that in some cases, used sparingly, it can. The main danger of inheritance is not that it is OO, but that it is not OO enough. It breaks encapsulation by mixing properties and methods between base classes and derived classes without clear boundaries. Composition is safer because it preserves encapsulation. In general, I think that protected abstract methods are a code smell, because they usually indicate close coupling of details that should be kept separate between the base and derived classes. But used correctly, inheritance can be more succinct and convenient.
- vips7L 11mo agoObject orientism is just encapsulation. It’s the only thing that is required. You can have objects without inheritance and virtual dispatch.
- vbezhenar 11mo agoSo Python is not OOP language? You can't hide fields.
- bluGill 11mo agoIt has conventions to hide data. Good enough.
- vips7L 11mo agoPython was a mistake if you ask me ¯\_(ツ)_/¯
- cpburns2009 11mo agoPython is the worst programming language except for all of the others.
- vips7L 11mo agoI don’t know man. It’s honestly the last thing I would choose. There is literally always something else that is more appetizing.
- nr378 11mo agoYou can hide fields in Python with a little bit of gymnastics: class EncapsulatedCounter: def __init__(self, initial_value): _count = initial_value def increment(): nonlocal _count _count += 1 return _count self.increment = increment counter = EncapsulatedCounter(100) new_value = counter.increment() print(f"New value is: {new_value}")
- 11mo ago
- jillesvangurp 11mo agoThe problem with that definition is that modern languages like Rust, Go, and Javascript fall between the cracks here with de-emphasizing things like classes and inheritance while still providing things like generics, interfaces, encapsulation, etc. For example, Javascript was influenced by a few languages, one of which was a language called Self which is a Smalltalk like language where instead of instantiating classes you clone existing objects. It's a prototype based language. So, there weren't any classes for a long time in Javascript. Which is why there are these weird class like module conventions where you create an object with functions inside that you expose. Later versions of ECMA script add classes as syntactic sugar for that. And Typescript does the same. But it's all prototypes underneath. Go has this notion of structs that implement interfaces implicitly via duck typing. So you don't have to explicitly implement the interface but you can treat an object as if it implemented the interface simply if it full fills the contract provided by that interface. Are they objects? Maybe, maybe not. No object identity. Unless you consider a pointer/memory reference as an identifier. Rust has traits and other constructs. So it definitely has a notion of encapsulation, polymorphism, etc., which are things associated with OOP. But things like classes are not part of Rust and inheritance is not a thing. It also does not have object identity because it emphasizes things like immutability and ownership. Many modern languages have some notion of objects though. Many of the languages that have classes tend to discourage inheritance. E.g. Kotlin's classes are closed by default (you can't extend them). The narrow IEEE definition is at this point a bit dated and reflects the early thinking in the sixties and seventies on OOP. A lot has happened since then. I don't think a lot of these discussions are that productive because they conflate a lot of concepts and dumb things down too much. A lot of it boils down to "I heard that inheritance is bad because 'inject reasons' and therefore language X sucks". That's maybe a bit harsh on some languages that are very widely used at this point.
- cjfd 11mo ago(1) and (2) sound reasonable enough. What do they mean by "dynamic object generation"?
- cess11 11mo agoRuntime instantiation. From the link above: "Instead of seeing a program as a monolithic structure, the code of a SIMULA program was organized in a number of classes and blocks. Classes could be dynamically instantiated at run-time, and such an instance was called an "object". An object was an autonomous entity, with its own data and computational capabilities organized in procedures (methods), and objects could cooperate by asking another object to perform a procedure (i.e., by a remote call to a procedure of another object)."