The criticism of OOP, I think, can be summarized as saying that inheritance hierarchies force you to design and impliment your program from the top down, and the difficulty in modifying that hierarchy means you have to get it right the first time or effectively start over.
In this, an interesting historical comparison, I think, can be made between OOP and Literate Programming. LP was a model for writing documented programs where the program would be essentially embedded within a larger document explaining the design of the program. There was some promise to this idea, but it petered out because, well, imagine having to write a book about a program at the same time as you’re writing the program itself. For it to work well, it would require the same kind of top-down design that strict inheritance based OOP requires, with the same problem that that’s just a really hard thing to do.
An alternate model has become quite popular, however. It can be seen in programs like GoDoc or ReadTheDocs etc. where you attach large comments to specific functions or types and then compile these out of the program into an easily digestible manual. This has a lot of the same advantages as composition based OOP, namely being able to design your program and build your documentation as you go, like building interfaces and types as you go. Theoretically, you could add tags like chapter headings or subheadings to each of these comments so that when you compile them out of the program, you end up with an organized document like you would’ve gotten with the original vision of LP.
I’m now picturing some kind of “Reverse Inheritance” language, where you declare child classes first before declaring the parent classes.
The two flavors of JVM-lang that developed to ease this are Kotlin and Scala. I'm more familiar with Kotlin than Scala, but both look like great upgrades. I think Kotlin is a bit more contemporary than Scala. But both will ease some pain.
Which makes it kind of funny that ClojureScript exists, literally just writing LISP code that translates into JavaScript because some retards in the 1990s bought the hype train of JavaScript and made it the only language that works in most browsers.
If I had to make a full stack I'd be curious about the full Clojure stack some people are using(Clojure backend, ClojureScript for front end). I hear it allows you to transmit code between the front end and the back end. Is this a viable way to partially bypass the JavaScript hellscape or is it just as niggerlicious? Id rather just code everything in Clojure and translate to JavaShit then have to choose between all the React / Angular etc. clusterfuck even if I have to program some of my own functions because some pajeet interface needs it.
Which makes it kind of funny that ClojureScript exists, literally just writing LISP code that translates into JavaScript because some retards in the 1990s bought the hype train of JavaScript and made it the only language that works in most browsers.
If I had to make a full stack I'd be curious about the full Clojure stack some people are using(Clojure backend, ClojureScript for front end). I hear it allows you to transmit code between the front end and the back end. Is this a viable way to partially bypass the JavaScript hellscape or is it just as niggerlicious? Id rather just code everything in Clojure and translate to JavaShit then have to choose between all the React / Angular etc. clusterfuck even if I have to program some of my own functions because some pajeet interface needs it.
JS was invented by Eich specifically for the browser. Supposedly, originally he was going to just add a Scheme interpreter to NetScape Navigator, but either he decided or was convinced that a more conventional, imperative type language was needed, so he hacked together JS on top of some scheme-ey foundations over the course of a couple weeks.
Haskell's Type Classes, Rust Traits or Go Interfaces could be stretched to match this imho.
Since you can easily see common interface and then add implementation in haskell/rust, or just duck it with go.
Though there's nothing stopping you from adding virtual class for interface in C++ after you notice similarities in different classes.
I would argue that's how you should program in many cases anyway. Just extract common interface as you go.
Haskell's Type Classes, Rust Traits or Go Interfaces could be stretched to match this imho.
Since you can easily see common interface and then add implementation in haskell/rust, or just duck it with go.
Though there's nothing stopping you from adding virtual class for interface in C++ after you notice similarities in different classes.
I would argue that's how you should program in many cases anyway. Just extract common interface as you go.
I don't know if the old saw about some OOP neophytes modelling an oil rig to prepare for development and ending up with a hierarchy from GalacticSupercluster -> .... -> Quark really happened, but I choose to believe it happened.
Haskell typeclasses are sick. Sometimes if I'm profoundly bored or I'm sick on a weekend or something, I'll throw something together in Haskell. It's very fun to work with if you have the background.
The two flavors of JVM-lang that developed to ease this are Kotlin and Scala. I'm more familiar with Kotlin than Scala, but both look like great upgrades. I think Kotlin is a bit more contemporary than Scala. But both will ease some pain.
Scala seems pleasant, though I have absolute minimal experience with it. I feel like I only hear about Scala being used in the data science/ML community, though, and having seen how nightmarish the code from data scientists can be, giving them a really flexible language like Scala scares me. I think that they mostly use it in more managed platforms like Databricks, though. That probably limits how niggerlicious it can get.
I clicked that link and it immediately caused me psychic damage but the thought of burgers makes for a good OOP analogy.
Like, when you're making a burger, do you inherit an onion burger which inherits a tomato which inherits a lettuce which is all a subclass of a bacon cheeseburger? No, because that's fucking stupid. You mix in each ingredient you need, as you need it. Composability just makes more fucking sense, both as it applies to data types and functionality.
Anymore willing to share Terry Davis thoughts or wisdoms on Programming? what made him different from let's say Linus or other cucks who sell their soul to Rust?
*I hear that Smalltalk's approach is very different to any remotely mainstream language that supports OO, but I haven't ever touched Smalltalk. Racket/Scheme and Haskell are the most obscure languages I've written any real amount of code in.
I would say the most innovative and unique idea in Smalltalk is that everything except for primitives is an object, even flow control. For example, if/else is implemented with a method called ifTrue:ifFalse: sent as a message to Boolean objects. It expects two blocks and, if sent to true executes the first block and, if false, executes the second block.
The two flavors of JVM-lang that developed to ease this are Kotlin and Scala. I'm more familiar with Kotlin than Scala, but both look like great upgrades. I think Kotlin is a bit more contemporary than Scala. But both will ease some pain.
SBT is considered the default build tool for Scala and I've never used it because of all the horror stories I've heard about it. If I get back into Scala in any really serious way I'm going to be looking at mill and Gradle, the latter of which I already have some familiarity with.
To my understanding, Scala's "killer app" for data science was Apache Spark. In that use case it has largely been eclipsed by Python (via PySpark). That doesn't mean you should never use Scala though.
Id rather just code everything in Clojure and translate to JavaShit then have to choose between all the React / Angular etc. clusterfuck even if I have to program some of my own functions because some pajeet interface needs it.
I would say the most innovative and unique idea in Smalltalk is that everything except for primitives is an object, even flow control. For example, if/else is implemented with a method called ifTrue:ifFalse: sent as a message to Boolean objects. It expects two blocks and, if sent to true executes the first block and, if false, executes the second block.
CLOS (and CLOS knockoffs) are the best OOP languages I've ever used. And they're just implemented as macros in a Lisp.
In fact, years ago I implemented my own shitty CLOS clone for a scheme I wanted to use that didn't have its own, just as a learning experience.
It was pretty neat, because I started off writing all the guts of method and class objects as ordinary define-structure (or whatever defstruct analog the scheme came with) objects. I eventually got the basic call algorithm working with just the raw structures, ie you could manually allocate some classes and objects and methods and run a call and observe that the printfs all got called at the right times.
This is a bad example of how many of the dumb ideas in OOP CLOS eliminates, but actually writing out the raw implementation is too elaborate for a simple example, so this probably suffices.
It's interesting, because as you're implementing it, and you fill in more and more of the parts, and especially once you've got the syntactical sugar implemented, there's a lot of useful little features you can add onto CLOS that's written in CLOS itself.
Like <class> is an object itself and so the later stages of implementation involve writing generic procedures that operate on aspects of the system itself.
In fact, in actual CLOS, they define a "metaobject protocol", which is basically where they publish all the aspects of the system as objects and you can selectively rewrite the call inheritance algorithm. I think that's probably sketchy deep magic, but I appreciate the flexibility conceptually.
There's a lot of recursive objects internally to make this happen.
Efficiency-wise, this is exactly as efficient as a comparable object system implemented in the compiler itself. But because of lisp macros, the compiler authors don't need to write it themselves or impose a single OOP model on their users. (I mean, almost. I have a little efficiency nitpick in define-method, but that's at load time and has to do with the implementation of define-method and define-generic. That is, there's not an efficient way to tack on data to a lambda to fetch later. I think I just used a global hash table.)
CLOS is a lot more interesting than the above example, btw. It's just hard to show the raw implementation with anything beyond cat, dog, mammal. But here's a little taste:
Code:
(define-class <mammal> ()
((name accessor: animal-name)))
(define-class <predator> ())
(define-class <cat> (<mammal> <predator>)
((coloring accessor: coloring)))
(define-class <dog> (<mammal>)
((breed accessor: breed)))
;; defining some methods
(define-method (attack (attacker <mammal>) (attackee <mammal>))
(format #t "~a attacks ~a\n" (animal-name attacker) (animal-name attackee)))
(define-method (attack (attacker <predator>) (attackee <mammal>))
(format #t "~a attacks and eats ~a\n" (animal-name attacker) (animal-name attackee)))
(define-method (attack (attacker <cat>) (attackee <mammal>))
(format #t "~a, a ~a cat, scratches ~a\n"
(animal-name attacker)
(coloring attacker)
(animal-name attackee)))
(define-method (attack (attacker <dog>) (attackee <mammal>))
(format #t "~a, a ~a, bites ~a\n"
(animal-name attacker)
(breed attacker)
(animal-name attackee)))
(define-method (attack (attacker <cat>) (attackee <dog>))
(format #t "~a, a ~a cat, scratches at ~a, a ~a and then hides under the bed\n"
(animal-name attacker)
(coloring attacker)
(animal-name attackee)
(breed attackee)))
;; testing
(define odie (make <dog> 'breed "dachshund" 'name "Odie"))
(define garfield (make <cat> 'coloring "orange tabby" 'name "Garfield"))
(define lemmiwinks (make <mammal> 'name "Lemmiwinks"))
;; (attack garfield lemmiwinks) =>
;;Garfield, a orange tabby cat, scratches Lemmiwinks
;; (attack garfield odie) =>
;;Garfield, a orange tabby cat, scratches at Odie, a dachshund and then hides under the bed
So one really big difference is that methods specialize on all the arguments they're provided, not just the implicit zeroth argument, this, like in C++ and similar OO systems.
Also multiple inheritance is supported. I never understood why that was such a problem in other languages.
Fantastic post @Marvin . Lays bare the actual mechanics. Reminds me of seeing likewise in C. Too many OOP pros are functionally cargo cultists who couldn't write an OOP implementation, and it shows.
This is genuinely fascinating stuff. I haven't worked on a production Lisp code base but I have some intuitive guesses on how I'd define your macros like define-class and define-macro. And it's gotten me to thinking about what a mistake it is, that we don't weed out people who are too dumb to get recursion more quickly.