7 ms·
The value of Go and Rust, I think, is that they are object-oriented languages that try to avoid conflating classes with types. If you think of a type as a set
by evincarofautumn 14y ago
The value of Go and Rust, I think, is that they are object-oriented languages that try to avoid conflating classes with types.
If you think of a type as a set of possible values, then a subclass is actually a supertype—a superset of the possible values of its parent class—and likewise a superclass is a subtype. The “is-a” relationship is saying “for all instances I of X, I is an instance of Y” which is essentially the definition of superset. That is what the Liskov Substitution Principle is about.
In C++ and other languages with support for object-oriented programming, a subclass is not necessarily a supertype: virtual functions can throw “I don’t make sense in this derived class”. And a supertype is not necessarily a subclass: templates offer the benefit of the doubt when it comes to static interfaces.
This mismatch leads, in my experience, to interfaces that are ill-specified at best and buggy at worst—so it’s nice to see those issues addressed.
- pjmlp 14y agoThe problem I see, is that the average programmers are only aware of the OO models popularized by C++, Java and friends, while there are quite many to choose from. This is one reason why so many have problems with prototype inheritance in JavaScript, which is also not the only language having it. I find positive that so many OO models exist to choose from.
- PommeDeTerre 14y agoNot all tools are equal, however. The class-based OO approach of C++ has proven to be practical for real-world software development. The same goes for the approaches taken by Java and C#, for example. While they can be abused, these approaches generally provide a usable balance between conceptual comprehensibility, consistency, modularity, code sharing/reuse, and the ability to model a variety of realistic domains. The same can't be said of JavaScript's prototype-based OO. It's not that people have trouble understanding it; they know it perfectly well. It's just that, when being used to write real-world software, it is nowhere near as convenient and practical as class-based OO. This is largely why we see so much effort go toward faking class-based OO within so many different JavaScript libraries and applications. JavaScript doesn't natively (yet) provide the tooling that developers need (class-based OO), so they're forced to try to implement it themselves using what JavaScript does offer. And the results usually are quite horrid. The fact that Self, Io and other prototype-based languages never really took off is similarly related, I suspect. Choice between different OO models isn't a bad thing. But when it comes to real-world software development, where there are clients, deadlines, and money to be made, developers need tools that work. Class-based OO has proven itself as a useful tool. Prototype-based OO has been more of a hindrance than a benefit, on the other hand.
- tambourine_man 14y agoReal world software, as you say, with money, clients and deadlines, is written in JavaScript all the time. It's not like it is a toy language or academic research project. It's the widest deployed platform in the world.
- PommeDeTerre 14y agoI think that you've missed the fact that we're discussing OO techniques and approaches here, rather than the overall usability of JavaScript. The software I mentioned includes JavaScript written in exchange for money, for clients, facing deadlines, in addition to open source JavaScript projects that may not face such constraints. In such code, we often see prototype-based OO not living up to the hype, and not being suitable for real-world work. Due to these failings, we see many, many developers resort to implementing some limited subset or variant of class-based OO using the functionality that JavaScript does offer. The fact that we see this going on time and time again, involving many different developers, all across the world, over the course of many years should tell us something. I think that "something" is that class-based OO is inherently superior to prototype-based OO for the types of problems typically encountered when developing practical software. Besides, C is the widest-deployed platform in the world. Add in C++, and their share of the market is absolutely huge. Basically every JavaScript implementation today is implemented using one or both of them. The major browsers embedding those JavaScript implementations are implemented in one or both. The operating systems the browsers are running on are implemented in one or both. The servers they're accessing are running operating systems, web servers, database servers, programming language implementations and applications written in C and/or C++. All of this is linked together using various networking devices running code written in C or C++. So every single line of JavaScript code that's executed depends on a stunningly huge amount of C and C++ code also being executed.
- klibertp 14y agoI will omit language war you both (parent and grandparent) seem to like waging and I'll go straight to the point. Which is that OO based on prototypes is strictly more powerful than popular class based ones. Which is proven by the very fact you mention: people are writing class-based OO implementations in JavaScript with easy, one can create such an implementation in under 100 LOC. So if you think that "superiority" is determined by expressive power, then (popular) class based OO sucks balls in this comparison :) I'm including the 'popular' qualification because thinking about class based object orientation in terms of C++ and Java is rather limited. If take a look at Smalltalk you'll see wonders like #become:. Ruby has implemented substantial subset of what Smalltalk provides and Python, although through really ugly hacks, is still able to replicate prototype-ness of JS. This hints us to the real problem I think you missed, I mean age old difference between static and dynamic typing. Things like Google's Closure Compiler seem to suggest that static typing is desirable in the large projects. And while JITing can alleviate most of the performance problems, maintenance still remains to be a pain in the ass. This same thing applies to class and prototype based OO - while more expressive for the most part, prototypes are not as maintenance friendly as classes are. All sorts of tools, when working with classes, can provide you autocompletion, for example - it's much, much harder with prototypes, which can change at any moment. So, to summarize, I think you blame the right thing but for incorrect reason. Prototype based OO is "better" than class based in terms of expressiveness, but "worse" in terms of maintenance (for now at least). As everything, both techniques have a place in programming and it's just stupid to bash one or the other.
- riffraff 14y agoI am not sure the issue of “I don’t make sense in this derived class” is solved by decoupling types and inheritance. For example, given the article's GO code: type file interface { Read(b Buffer) bool Write(b Buffer) bool Close() } my first thought was: wait, not all files are writable, that interface is ill specified. I do think subtyping and subclassing should be different things, but it's not a panacea against bad specifications.
- georgemcbay 14y agoThat interface isn't great, but that interface isn't coming from Go. I realize you aren't claiming it is, and you're absolutely right that Go's interfaces don't automatically "solve" issues of bad design. Programmers are free to come up with bad interface designs in Go just as easily as they came up with bad class designs in Java or C++. This is just to avoid confusion for people who aren't generally familiar with Go but read this article. The Go standard io library has a Reader interface: type Reader interface { Read(p []byte) (n int, err error) } a Writer interface: type Writer interface { Write(p []byte) (n int, err error) } and a ReadWriter interface which uses type embedding to combine both: type ReadWriter interface { Reader Writer } Idiomatic Go will break interfaces up into pretty discrete bits of functionality and use type embedding to build up more complex interfaces. The article does show this a little bit with its seekable interface but fails in some other areas and I kind of wish it just used the real io package interfaces or else examples of something not covered better by the actual Go standard library.
- riffraff 14y agoWell, sure, but Reader and Writer are interfaces existing in java too :) Anyway I think we agree on the general issue, but actually I suspect go's approach _does_ solve some bad design issues that crop up in common OO, namely the fragile base class one. I just don't think it impacts the misspecification one.
- deleted 14y ago[deleted]
- 14y ago
- pat_punnu 14y ago"If you think of a type as a set of possible values" You can't do that! You fall foul of Russell's paradox - sets as types simply don't work!
- deleted 14y ago[deleted]
- vanderZwan 14y agoElaborate please?
- StefanKarpinski 14y agoAs a consequence/means of avoiding Russel's paradox, the standard axiomatization of set theory (ZFC), doesn't allow sets to contain themselves (so no set of all sets). Thus, if types are sets of values, and types are values, you've got a problem since the type of types seems to be a set that contains itself. Of course, I think this just means that well-founded set theories like ZFC are a poor choice for modeling type systems for languages with first-class types. There are set theories that allow sets to contain themselves [http://en.wikipedia.org/wiki/Non-well-founded_set_theory http://en.wikipedia.org/wiki/Non-well-founded_set_theory]. Such set theories don't replace ZFC, but rather extend it, adding "hypersets", which are the sets that contain themselves – the well-founded sets behave as usual.
- Tobu 14y agoNope, Russel's paradox requires set membership axioms + a non-constructive definition of sets. Types are constructive here.
- tomp 14y ago> then a subclass is actually a supertype—a superset of the possible values of its parent class—and likewise a superclass is a subtype I'm sure this makes sense and is well-though-out, but I just can't understand it. What difference does it make if `Dog < Animal` are classes or types? Still any dog is an animal and not any animal is a dog. Or am I missing something?
- bitdiddle 14y agoyou're not missing something, I believe the comment just got it flipped, and was referring[1] to problems relating to covariance and contravariance, when one tries to view OOP in terms of type theory. [1] http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Overview_of_covariance.2Fcontravariance_in_some_programming_languages http://en.wikipedia.org/wiki/Covariance_and_contravariance_(...
- munificent 14y ago> If you think of a type as a set of possible values, then a subclass is actually a supertype—a superset of the possible values of its parent class—and likewise a superclass is a subtype. You have this exactly backwards. A subclass is a subtype. A superclass is a supertype. Let's say you have superclass A and derive subclass B from it. In the universe of all possible values, some will be A but not B (they will either be direct instances of A, or instances of some other subclass of A). But every B will also be an A by definition since B is-a A. Therefore the set of values that are B is a subset of the set of values that are A.
- evincarofautumn 14y agoI was a bit backward with my language. What I was trying to get at is that many OO languages encourage you to add fields in derived classes. This makes the derived class a supertype, being a larger set that fully includes the set of its parent class. You’re adding elements to a product type. But this often breaks substitutability, so to conflate class inheritance (a useful concept) with subtyping (another useful concept) is fraught with problems.
- cpeterso 14y agoWhen a derived type add new fields, it is restricting the type's range, not expanding the base type's capabilities.
- sixbrx 14y agoYes and no. I think the poster does make an interesting point. If the parent class is instantiable - which is a code smell already but also very common and even encouraged by some OO evangelizing[1], as is the case for the "colored point" example - then he's right, the derived class which adds fields does admit more values than the parent class does (excluding child values). [1] encouraged by the commonly heard advice: "if you don't like exactly what the class does, then derive from it and supply your own customizations". Ie. this is the use of inheritance for ad-hoc customization, as opposed to a design-first approach which would have the parent be abstract (which has its own problems of course such as requiring great foresight).
- gngeal 14y agoIf you think of a type as a set of possible values, then a subclass is actually a supertype—a superset of the possible values of its parent class—and likewise a superclass is a subtype. Nope. A subclass is usually (not always!) a subtype, not the other way round. Or, more specifically (and accurately), most statically-typed class-based OO languages treat subclasses in their type systems as subtypes by default, i.e., they assume that a subclass instance can act as an instance of its superclass, LSP-wise. This, I believe, is as accurate statement as one can make (on this level of generality). The “is-a” relationship is saying “for all instances I of X, I is an instance of Y” which is essentially the definition of superset. That is what the Liskov Substitution Principle is about. It's a definition of a superset, if Y is the superset and X is the subset. In C++ and other languages with support for object-oriented programming, a subclass is not necessarily a supertype: virtual functions can throw “I don’t make sense in this derived class”. That's a part of why I believe that static typing doesn't make too much sense when it comes to OO languages (with (sub)classes). If static typing is supposed to take care of subtype assignability statically and the semantics of the behaviour of derived classes can break the LSP assumptions, static typing becomes sort of worthless.