News for hackers. Stuff that matters.
Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @12:10PM (#23193817)
Usually when I see somebody arguing against object-oriented programming, I don't see them arguing for functional programming, but instead arguing for procedural (like Cobol, Basic, or Pascal) programming. What they usually miss is that there's a reason procedural programming was abandoned some time around the mid-90's, and for good reason: you can't realistically develop useful software in pure-procedural way without introducing a lot of global state. Even if you look at well-designed non-OO code like, say the Linux kernel, you'll see that there are object oriented concepts like information hiding and polymorphism all over the place; they just don't formalize them with the "class" or "private" keywords. Unfortunately, what I see most programmers do is give up on OO design (and never even consider FP) and instead create global state that they call "singletons" to pretend that they didn't just create a global variable. Because, as we all know, global variables are bad, but too few of us actually remember why.
Re:Pretending OOP Never Happened by btilly on Friday May 15, 2020 @01:27PM (#23194797)
you can't realistically develop useful software in pure-procedural way without introducing a lot of global state. Even if you look at well-designed non-OO code like, say the Linux kernel, you'll see that there are object oriented concepts like information hiding and polymorphism all over the place; they just don't formalize them with the "class" or "private" keywords.

You are misattributing that to OO programming.

The original edition of Code Complete, written in 1993, makes absolutely no mention of OO programming or any OO principles. However it has a very good discussion of information hiding, and procedural code written in the various styles that it recommends do not create a lot of global state. (And yes, I have worked with such code.)

One of the best things about OO was that it pushed programmers who had not absorbed best practices towards that. However OO encourages combining the principle of information hiding with OO notions of inheritance. Many learned the hard way to prefer composition over inheritance. (Many, unfortunately, never learned that. Just as many procedural programmers never learned about information hiding.)

Re:Pretending OOP Never Happened by arc776 on Friday May 15, 2020 @05:35PM (#23197753)
I feel like inheritance is the actual problem with OOP. Trees of types with increasing specialisations just don't describe many real world problems that well.

When you do build programs like this, code gets more and more rigid until it becomes a maintenance nightmare, and extremely expensive to pivot functionality. Sometimes having a complex object hierarchy literally stops you making some new feature.

A lot of mental effort is sucked up in mashing problem spaces into hierarchies, and once there the program is constrained by them.

Re:Pretending OOP Never Happened by zozbot234 on Friday May 15, 2020 @06:13PM (#23198139)
It has little to do with subtyping per se and everything with the way that implementation inheritance, specifically, breaks modularity. The whole idea of inheritance contra composition is that every call to a virtual method-- including calls internal to the class-- goes through a dispatch step so that the actual code that gets executed depends on whether the object is one of a "derived" class, where that method might have been changed in ways that might break any amount of expected invariants. This introduces brittleness both in base class code (which generally has to call methods that might get overridden in derived classes) and in the derived class itself (which has no way of knowing what invariants might be expected of it as part of base class code).

Implementation inheritance is often justified as a way of "reusing" code, but as it turns out, we've merely introduced undesired coupling instead of the seamless reuse we might have expected.

Re:Pretending OOP Never Happened by arc776 on Friday May 15, 2020 @07:47PM (#23198884)
> every call to a virtual method-- including calls internal to the class-- goes through a dispatch step

You've touched on a whole host of performance issues here too. Not only are we making indirection after indirection when accessing virtual methods and data (my poor cache), but invariably these are all stored in heap memory, so we get the worst possible performance profile baked in.

> the actual code that gets executed depends on whether the object is one of a "derived" class, where that method might have been changed in ways that might break any amount of expected invariants.

On top of that inheritance makes the concrete type unknowable at compile time just to add to the pain.

> Implementation inheritance is often justified as a way of "reusing" code, but as it turns out, we've merely introduced undesired coupling instead of the seamless reuse we might have expected.

Yes this 100%. Inheritance actually stands in the way of code reuse. If I have a class that is kind of similar to another, but different enough to "require" being another class, oh dear they're now incompatible. So you try to kind of fit it into a subclass, but it makes more sense in another, and now you're spending mental effort on a problem created by architecture and not the actual thing you're trying to solve.

This is really clear in the game development world, where game objects often just don't fit into the inheritance format, even with multiple inheritance. This is why most complex games are built with the entity component system pattern as it focuses entirely on composition.

Perhaps the crux of the issue is that inheritance encourages design by "identity" (mental concept) rather than "attribute" (data). Thus to solve problems we must mentally classify them into some taxonomy as if they are animal species before we even write any code. This is just extra work in exchange for all the issues mentioned.

Re:Pretending OOP Never Happened by temac on Friday May 15, 2020 @08:42PM (#23199289)
> where that method might have been changed in ways that might break any amount of expected invariants

It's not supposed to, and if it does, the design is clearly broken. Now the (rhetorical) question is: does it happens often. The not so rhetorical question is: is it possible to make it happen rarely. The even more interesting question is: is it easy / more practical compared to alternative approaches, and if not then what is the point.

My opinion on SOLID is that there is precisely one hard "principle", and it is worthwhile: the LSP (it derives directly from logic, that's why). I believe that Open-close is even borderline insane, and that if there is a crazy way to make it not insane, this way is probably applied by so few people that it may well be irrelevant -- most people will try to apply it in ways that will quickly put them at risk for LSP violation, but LSP is far more important (or they will try to apply it in bad places, but that is another story). Plus programs designed with complex hierarchies are often missing the point of execution contexts, and then understanding them is absolute hell -- their original authors sometimes do not understand them themselves. (The rest of SOLID are soft attempts at fixing self-inflicted wounds, sometimes even reasonable if you insist on doing Javaesque / old-C++-esque OOO, but I digress.)

I'm not sure why anybody thought that kind of OOO was a good idea, or that the main characteristic in interesting big programs was the usage of "classes". I even find the suggestion of causation instead of mere correlation dubious; they were already quite a good number of big programs before, and what permitted the explosion of program size was more the ever growing capabilities of computers, that happened, in affordable versions, during the Java-like OOO hype. Besides the simplistic reductionism (we can start with: which classes model entities, which classes model values, which classes are controllers, etc.) which is not too much a big deal in practice, modeling with class diagrams is often missing the river in the middle of the forest for some groups of intertwined trees.

Re:Pretending OOP Never Happened by kragen on Saturday May 16, 2020 @12:01AM (#23200382)
What does "OOO" stand for? Usually it means "out-of-order [execution]" but that doesn't make sense here.

The Smalltalk-80 container hierarchy demonstrates that you can get a lot of mileage out of simple single-dispatch virtual methods with inheritance. The Smalltalk-78 system, which you can try at https://lively-web.org/users/bert/Smalltalk-78.html (although it's not working for me at the moment), got a multiwindow GUI with an IDE running usably on an Intel 8086 with 256KiB of RAM, in only about 100 classes and 2000 methods totaling 200 KiB of code. This is not what I would describe as a "big program", but it is a fairly impressive program nonetheless. Seeing that kind of thing is what led people to adopt object-oriented programming.

Re:Pretending OOP Never Happened by xkriva11 on Saturday May 16, 2020 @04:00AM (#23201366)
Re:Pretending OOP Never Happened by temac on Saturday May 16, 2020 @03:55AM (#23201342)
Yeah I meant OOP but mechanically wrote OOO.
Re:Pretending OOP Never Happened by dragonwriter on Friday May 15, 2020 @06:27PM (#23198260)
> the derived class itself (which has no way of knowing what invariants might be expected of it as part of base class code).

In principle, with a sufficiently-robust type system, the invariants expected by the base class would be encoded in the typing, and the derived class would only be able to narrow them.

(Of course, OOP languages tend to either be untyped or have insufficiently robust type systems for this; robust type systems are more frequently associated with functional languages.)

Re:Pretending OOP Never Happened by temac on Friday May 15, 2020 @09:24PM (#23199571)
If the type system already trivially enforces your custom invariants, aren't the types defined by your classes already in it?
Re:Pretending OOP Never Happened by zokier on Friday May 15, 2020 @07:04PM (#23198537)
> every call to a virtual method-- including calls internal to the class-- goes through a dispatch step

why would you make internal methods virtual though?

Re:Pretending OOP Never Happened by bregma on Friday May 15, 2020 @08:08PM (#23199028)
Virtual methods should only be internal methods. There's no point in making the public API have virtual methods. That would be a bass-ackwards parody of proper OO design.
Re:Pretending OOP Never Happened by kragen on Saturday May 16, 2020 @12:08AM (#23200418)
It's common to put virtual methods in a public API; indeed, in Java we have the "interface" construct consisting entirely of public virtual methods and, occasionally, named constants. (In more recent versions it can also include default implementations.) In Golang the only virtual-method mechanism works by means of a similar interface construct, which also only contains public virtual methods.

So, for example, Java's Map is an interface type with, for example, .get() and .put() methods; TreeMap and HashMap implement those methods differently. There are similar things in Smalltalk-80 and the C++ STL.

So clearly you think Java, Smalltalk, and C++ are "a bass-ackward parody of proper OO design." I'm interested to hear what systems you consider paragons of proper OO design.

Re:Pretending OOP Never Happened by zozbot234 on Saturday May 16, 2020 @08:20AM (#23202800)
Interface inheritance is broadly fine though, because it specifically lacks the conflation of "base class" and "derived class" code. The interface can be assumed to directly imply some invariants, and it's easy to see of any implementation whether it respects them. Everything is nicely localized.
Re:Pretending OOP Never Happened by kragen on Sunday May 17, 2020 @03:44AM (#23210412)
I agree, but I didn't interpret the comment I was responding to as objecting to implementation inheritance as such, rather the contrary: they're objecting to an enormously wide category of OO designs which includes not only interface inheritance but also most, but not all, uses of implementation inheritance; for example, the Smalltalk-80 design that lumps inject and select into some abstract base class for containers.
Re:Pretending OOP Never Happened by anonymoushn on Saturday May 16, 2020 @01:18AM (#23200703)
Wait, really? I don't know much about "proper OO design" but am forced to use Java at work. I commonly use classes that have e.g. a virtual public getFoo, then someone's made a RealFooGetter that actually talks to the network and a CachingFooGetter that delegates to another FooGetter and maintains a cache. Should I ask my coworkers to stop doing such things?
Re:Pretending OOP Never Happened by rramadass on Monday May 18, 2020 @12:49AM (#23218474)
No.

You might find the book Program Development in Java: Abstraction, Specification, and Object-Oriented Design by Barbara Liskov and John Guttag very useful for understanding OOD/OOP.

Re:Pretending OOP Never Happened by rramadass on Monday May 18, 2020 @12:43AM (#23218450)
What in the world are you talking about? The whole idea of a Type/Class Hierarchy is based on publicly accessible virtual methods or more precisely, on dynamic dispatch.
Re:Pretending OOP Never Happened by waltpad on Saturday May 16, 2020 @01:27PM (#23205369)
You didn't motivate your claim. Could you elaborate?
Re:Pretending OOP Never Happened by btilly on Friday May 15, 2020 @06:33PM (#23198300)
I feel like inheritance is the actual problem with OOP. Trees of types with increasing specialisations just don't describe many real world problems that well.

I firmly agree.

I wrote https://www.perlmonks.org/?node_id=318257 over 15 years ago. I still agree with the fundamental criticism of "OO everywhere" that I offered there.

Re:Pretending OOP Never Happened by jholman on Saturday May 16, 2020 @06:50AM (#23202353)
Thank you for the excellent piece.

Though w.r.t. the speculation in your final "Disclaimer", I'd expect the algebraically inclined to prefer either the elegant combinatorical explosion of a J, or the build-it-yourself language of a metaprogrammed Lisp.

Re:Pretending OOP Never Happened by GlennS on Friday May 15, 2020 @10:13PM (#23199875)
Thanks, enjoyed this. That SICP quote really nails it too.
Re:Pretending OOP Never Happened by chrstphrhrt on Friday May 15, 2020 @07:24PM (#23198692)
Agree 110%, and I want to take your notion as a cue to expound a little more from personal trauma :)

I find OOP useful for configuration and library-level APIs (e.g. for injecting dependencies and wrapping things), when there is really zero expectation that a user should have to understand the class definition entirely. But that's about it.

Where it seems to break down for me is when classes are actively used at the application implementation level for general encapsulation. E.g. data access layers. This can be done perfectly fine with modules/packages. Adding inheritance is just asking for trouble in a team setting IMHO.

Trouble comes when there is no clear peer review and style policy to avoid classes for anything other than config or distributing libraries (as separate projects). What ends up happening is a proliferation of subclasses or method overrides when developers are in a rush to ship features without understanding the whole codebase. This is a technical loan with very high interest.

It makes sense rationally in the moment, as classes have an inviting feel to the user as a kind of grab bag of related functionalities that are easily introspected (at first). Compare it to searching through docs for all the different namespaces in a package and learning what types their various functions support, it requires more thought and grasping the concepts of the library. Alternatively, a class with a broadly defined purpose starts to look like a common utility to throw things at. It's a grab bag of stuff that is more amenable to hacking with blinders on, in a way.

Next thing you know the chains of inheritance and method overrides have grown into a very hard to disentangle hydra. The accumulated overhead of maintaining it is compounded by the debugging challenge of knowing where exactly a given instance is coming from and which layers it has been through on its way to a given breakpoint.

Re:Pretending OOP Never Happened by xt00 on Friday May 15, 2020 @08:26PM (#23199151)
The concept of "if you want to touch this code base you have to understand the entire thing" is pretty tough on basically everybody but the people who either wrote most of the code or who live in that code base all the time. It would seem like code-bases should (either because people architected it to be possible to work with without understanding the whole thing or the programming language helps facilitate it) be able to be worked with while only understanding a portion of the code base. In some cases classes and interfaces help with that, but in plenty of cases you end up in one of the sort of child classes trying to figure out how to get out of the hole you are in where you can't interact with anything anywhere in the code base and you have to basically plumb pipes through lots of places to allow moving data where you want.
Re:Pretending OOP Never Happened by GlennS on Friday May 15, 2020 @09:31PM (#23199609)
Agree completely, that's a really nice summary of the key problems.

The mental effort of building up taxonomies in particular is exactly what put me off inheritance (and fussy type systems in general).

It's too easy to jump down that rabbit hole without considering whether it's a productive activity.

Re:Pretending OOP Never Happened by aryehof on Saturday May 16, 2020 @03:23AM (#23201186)
Perhaps an issue is that the well known saying "prefer object composition over class inheritance" isn't done, because a majority of programmers simply don't know how to apply it.

Inheritance is used inappropriately (outside of frameworks) almost everywhere.

Re:Pretending OOP Never Happened by im3w1l on Friday May 15, 2020 @09:34PM (#23199633)
This is highly codebase dependant. I have seen code with crazy hierarchies like you say, and ones without it. It's possible to "just don't do that".
Re:Pretending OOP Never Happened by tcgv on Saturday May 16, 2020 @12:07AM (#23200413)
The concept of information hiding came a bit before that, almost 50 years ago, first being described in a paper by David L. Parnas entitled “On the Criteria to Be Used in Decomposing Systems into Modules”.

I wrote a blog post earlier this year based on this paper, describing how using the information hiding criterion naturally leads to an improved system structure when compared to using a procedural criterion [1]

I'm an advocate for OOP because it greatly favors information hiding and encapsulation, helping to manage the ever-growing complexity of software projects, making "it possible to develop much larger programs than before, maybe 10x larger" (quoted from the linked article)

[1] https://thomasvilhena.com/2020/03/a-strategy-for-effective-s...

Re:Pretending OOP Never Happened by rramadass on Monday May 18, 2020 @01:00AM (#23218531)
Thank you. Every Software Engineer should read Software Fundamentals: Collected Papers by David L.Parnas. People are nitpicking things and just parroting what is written in other books without really understanding the idea behind it all. For example what is a Interface/Class/Library/Framework/Module(logical and physical) if not Information Hiding at different levels? The concept is the same but the realization is different. OOD/OOP just gives you convenient syntactic sugar to express them. The rest is up to you.
Re:Pretending OOP Never Happened by seanmcdirmid on Friday May 15, 2020 @11:38PM (#23200303)
Polymorphism via indirect virtual dispatch basically means you want to treat different kinds of objects differently via the same interface. If you take OOP as programming with objects, it most definitely is doing OOP; if you take OOP as being Java or some other more specific definition, then it isn’t.

When you implement an extended jump table (a table set that extends an existing table def) in C, that is also just inheritance.

There is a good reason the first C++ compiler was just implemented as a bunch of macros in C.

Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @01:30PM (#23194844)
These conversations aren't helped by the fact that OOP isn't well-defined, and observations about 'typical' OOP--the banana that has a reference to the gorilla and transitively the entire jungle, or the pervasive abuse of inheritance--are "aren't really OOP and you can write bad code in any paradigm!" even though other paradigms (for whatever reason) don't share these issues at nearly the scale as OOP. Inheritance and god objects were certainly how OOP was taught at universities and in the predominate literature when I was in school circa 2010. And if these (mis)features aren't part of the definition of OOP, then what distinguishes it from functional and/or data oriented paradigms? Encapsulation? Seems like FP has encapsulation and DO is orthogonal. Certainly localization of state is common to both FP and DO--it's not an OOP innovation.

If OOP is so abstract as to be indistinguishable from other paradigms, then what value does it add?

Re:Pretending OOP Never Happened by ipnon on Friday May 15, 2020 @05:15PM (#23197505)
An object is in some ways a function that defines its own parameters (fields and methods) and arguments (state). This then is injected into another such function.
Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @05:52PM (#23197933)
if it's a function, what are the inputs/outputs? do you mean that in the way of "an object is a poor person's closure"?

in case anyone's unfamiliar, here's one basic way to emulate objects with closures. i think i saw it in SICP (?), reproducing it here because i just love how simple it is:

  // might be easier to first look at
  // the usage example at the bottom

  let Point = (x, y) => {
    let self = (message, args=null) => {
      switch (message) {
        // getters
        case 'x': return x;
        case 'y': return y;

        // some operations
        // (immutable, but that's not required)

        case 'toString':
          return `Point(x=${x}, y=${y})`;

        case 'move':
          let [dx, dy] = args;
          // use our getters
          return Point(self('x')+dx, self('y')+dy);

        // let's get DRY!
        case 'plus':
          let [other] = args;
          return self('move', other('x'), other('y'));

        default:
          throw Error(`unknown message: ${message} ${JSON.stringify(args)}`);
      }
    };
    return self;
  };
  
  
  let p1 = Point(3, 5);
  // sending messages
  p1('x') === 3;
  p1('y') === 5;
  
  p1('move', [1, 2])('toString');
  // --> "Point(x=4, y=7)"
  
  let p2 = Point(1, 2);
  p1('plus', [p2])('toString');
  // --> "Point(x=4, y=7)"
  
dynamic dispatch & message passing, just like that! and you can easily do `__getattr__/method_missing`-style dynamicism just by looking at `message`.

for the other way around, see how Java lambdas desugar to objects with a "call" method and closed-over variables as members.

Re:Pretending OOP Never Happened by btilly on Friday May 15, 2020 @08:45PM (#23199309)
do you mean that in the way of "an object is a poor person's closure"?

Objects are a poor man's closures. And closures are a poor man's objects.

Modern languages have both. And they serve different purposes.

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @09:12PM (#23199491)
i intended to put "(and vice versa)" in a footnote, but forgot about it! you can see a relic of that in the last paragraph.

though i must say, i'm pretty happy in languages that have closures but don't have objects, as long as there's a nice way to do ad-hoc polymorphism (like traits/typeclasses)

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @08:06PM (#23199008)
also, i just made a possibly interesting connection with JS methods (and their weirdness). in the above implementation, a message is first class, so you can send the same message to multiple objects:

  let moveUp = ['move', [0, 1]];
  p1(...moveUp)
  p2(...moveUp)
but you can't get a `move` that's "bound" to a particular object¹ – the "receiver" is decided when you "send" the message. which reminds me of how JS methods work: `this` is bound to what's "to the left of the dot" when you call a method, so unlike Python,

  obj.foo(1)
is not the same as

  let f = obj.foo
  f(1) // Error: 'this' is undefined
maybe there's a connection to some language that inspired JS' OO model, with prototypes and all that?

---

¹ well, unless you explicitly wrap it in another closure like

  (args) => p1('move', args)
Re:Pretending OOP Never Happened by krallja on Friday May 15, 2020 @08:46PM (#23199321)
p1.move.bind(p1)
Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @09:29PM (#23199596)
i know :) the point is that that's not the default (unlike many other languages), and i was wondering about a possible origin for that choice.
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @08:25AM (#23202831)
btw this could (sorta) be considered a special case of the fact that a tuple/struct (product type) can be represented as a function:

  let pair = (a, b) => (
    (ix) => (
      ix === 0 ? a :
      ix === 1 ? b :
      undefined
    )
  );

  
  let p = pair(3, "foo");
  p(0) // 3
  p(1) // "foo"
i've seen sth like this used as the definition of tuples in a math context. (there are also other ways to do it too, like church/scott encoding)

however the object has the extra wrinkle of self-referentiality, because the `self` function is defined recursively

Re:Pretending OOP Never Happened by pbourke on Friday May 15, 2020 @06:45PM (#23198400)
This is a neat example, but wouldn't dynamic dispatch imply polymorphism?
Re:Pretending OOP Never Happened by josephg on Friday May 15, 2020 @06:52PM (#23198453)
It’s dynamic dispatch because the decision about what code to call happens at runtime (via the switch statement) rather than compile time.
Re:Pretending OOP Never Happened by pbourke on Friday May 15, 2020 @08:40PM (#23199272)
But that's essentially a table lookup, not a vtable lookup, right?

Dynamic dispatch implies that you dispatch based on the class of the receiver. For instance, you can't have a Point3 value that uses Point's implementation of 'x' and its own implementation of 'toString' under this example.

Re:Pretending OOP Never Happened by lalaithion on Friday May 15, 2020 @10:42PM (#23200021)

  let Point3 = (x, y, z) => {
    let parent = Point(x, y);
    let self = (message, args=null) => {
      switch (message) {
        // getters
        case 'z': return z

        // some operations
        // (immutable, but that's not required)

        case 'toString':
          return `Point(x=${x}, y=${y}, z=${z})`;

        case 'move':
          let [dx, dy, dz] = args;
          // use our getters
          return Point(self('x')+dx, self('y')+dy, self('z')+dz);

        // let's get DRY!
        case 'plus':
          let [other] = args;
          return self('move', other('x'), other('y'), other('z'));

        default:
          parent(msg, args)
      }
    };
    return self;
  };
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @08:08AM (#23202743)
maybe "extremely late binding" would be more appropriate? it's true that usually "dynamic dispatch" means a vtable, but i think this is dynamic dispatch in spirit - in fact "dispatching" messages to implementations is pretty much the only thing a closure-object does :) i chose to write the impls inline, so they're p much impossible to reuse, but that's not required.

> you can't have a Point3 value that uses Point's implementation of 'x' and its own implementation of 'toString' under this example.

you can't do much of anything under this this example! i didn't think a whole object system would fit in a HN comment, i intended it to be minimal :)

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @08:14PM (#23199063)
this is particularly visible in the implementation of `plus` – `other` might not be a Point at all, so `other('x')` and `other('y')` might do anything; which i'd say is textbook (ad-hoc) polymorphism.

i remember reading this somewhere: "branching on values is the ultimate dynamic dispatch" :)

Re:Pretending OOP Never Happened by ipnon on Friday May 15, 2020 @08:22PM (#23199118)
Excellent example, thank you.

Edit: To answer your question, the set of all public methods could be seen as 1, but not the only, interpretation of the functional interface.

Re:Pretending OOP Never Happened by uryga on Friday May 15, 2020 @10:02PM (#23199815)
thank you :) i hope i didn't hijack the thread! but i just love sharing stuff like this

> the set of all public methods could be seen as 1, but not the only, interpretation of the functional interface.

could you describe another interpretation? "the set of public methods"¹ is the only thing i can think of when i hear about an object's "functional interface". it could be a terminology issue though – i'm reading "functional interface" in a general sort of way, but i could imagine it having some specific definition in OO theory.

---

¹ or recognized messages, in a more smalltalk-ish context

Re:Pretending OOP Never Happened by ipnon on Saturday May 16, 2020 @12:43PM (#23204979)
The other interpretation has slipped my mind since yesterday, sorry mate. I do think it's an interesting idea that I now see I have no formal understanding of. Cheers.
Re:Pretending OOP Never Happened by uryga on Saturday May 16, 2020 @01:03PM (#23205149)
shame! cheers :)

PS. if you're interested in weird perspectives on this, you might find Coinduction (and "codata") interesting. from wikipedia:

> Informally, rather than defining a function by pattern-matching on each of the inductive constructors, one defines each of the "destructors" or "observers" over the function result.

it's a deep rabbit hole, but i remember it being applied OOP-ish things – "destructors" would roughly correspond to exposed methods.

Re:Pretending OOP Never Happened by hnick on Friday May 15, 2020 @10:00PM (#23199799)
In perl an object is literally a data structure with funtions glued on. Which is kind of the opposite of what you say!

Usually this is a hash (dictionary) which is similar to the object with fields that we all know, but it can just as easily be an array or scalar value.

Re:Pretending OOP Never Happened by baryphonic on Saturday May 16, 2020 @03:48AM (#23201311)
> These conversations aren't helped by the fact that OOP isn't well-defined, and observations about 'typical' OOP--the banana that has a reference to the gorilla and transitively the entire jungle, or the pervasive abuse of inheritance--are "aren't really OOP and you can write bad code in any paradigm!"

From Alan Kay (who coined the term)[1]:

> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them.

Almost anyone who has dealt with present-day OOP (Python, Ruby, C++, C#, others, or, God forbid, Java) has had the same thoughts as you, I'm guessing. I've come to the conclusion that OO as originally envisioned had a lot of good ideas, and these are surprisingly compatible with and complementary to a lot of the (almost-)pure FP ideas that have gained traction over the last few years.

OOP also had huge mistakes:

- Class inheritance is the biggest one. Kay himself mentions inheritance appearing only in the later versions of Xerox's Smalltalk. No need to really elaborate. Implement interfaces, compose behavior and don't inherit. He has posted some more of his thoughts on this exact topic. [2] - Java was a giant industry-harming one. Now multiple generations of programmers have been brought up to think that the definition of an object is to write a class (which is not an essential characteristic), declare internal state and then write accessors and mutators for every one of them. We might as well write COBOL. But, having tried to solve problems in Java, I get why it's done. The OO is so weak and awful that I'd rather just crank out slightly-safer C in Java than jump through the horrific hoops. (Just as an aside, I find it sad that Sun had the teams that developed both Java and Self. Self used prototypes instead of classes, which influenced Javascript, but also had a killer JIT that made it super fast and a ground-breaking generational garbage collector. Sun ended up cannibalizing the Self team to assist with Java, and some of the features of the JVM were ports of work that had originally been done for the Self VM.) - C++ was kind of a mistake, because it introduced the interminable "public" and "private" and now the endless litany of stupid access modifiers. They were needed because C++ had this need to be compatible with C and do dynamic dispatch and have some semblance of type safety (though C is only weakly typed) and have it run (and compile) before the heat death of the universe. C++ isn't as horrible as what came after it though IMO (i.e. Java). - Xerox horribly mismanaging Smalltalk was another mistake. Xerox was smart enough to realize that Smalltalk was insanely valuable, but instead of realizing that it should be a loss leader/free drugs, they decided to lock it away in the high tower and then let everyone else pillage their research that could be commercialized (GUIs on the desktop, laser printers, Ethernet, among many others). This literally led Apple to develop Objective-C.

I really went down a rabbit hole on this topic around two years ago. Two ideas really helped crystalize the idea for me, both from Kay.

One is that the Internet is an object-oriented system. It communicates via message-passing, it is fault-tolerant, and all of its behavior is defined at runtime (i.e. dynamically bound). We don't need to bring the entire Internet down when someone develops a new network protocol or when a gateway dies or some other problem happens. It continues to work and has in fact never gone down (though early on during ARPANet, there were instances of synchronous upgrades [3]).

The other, deeper one (and the much more open one) is that "data" is error-prone and should be avoided and is, in fact, "the problem" (i.e. the root of the Software Crisis). This sounds preposterous to most programmers.[4] In fact, I didn't even get it when I first heard it. I think the basic idea is that he uses "data" as a physicist or statistician would use it: quantities measured in the world with varying accuracy/precision. This seemed preposterous or even heretical until I started noticing it in the large codebase I was working on at the time. A lot of the code was in a nominally object-oriented language, but the style was very procedural (i.e. "If a then do X, do Y; else do Z"). I noticed that all of our code was working on large data structures (think a relational database schema), but there was no coherent description of it or what it meant anywhere. If I touched one of the pieces of code, I invariably had to start adding more conditional branches in order to preserve correctness. In order to make any sense of the code, I made a ton of implicit assumptions about what the patterns I saw actually meant, rather than relying on the program itself to communicate this through its code or meaningful constraints on the data. We would be better served with "information" with less noise and more signal than what we get with data and data structures (think Shannon). The only real success we've had with data is a few encoding schemes (e.g. ASCII/Unicode, 8-bit bytes, IEEE-754, etc.), but that approach clearly doesn't scale beyond a dozen or two data encoding schemes. OO (of the high signal-to-noise variety championed by Kay) at least has a story for this, which is that certain patterns do indeed have predictable characteristics, and complex patterns can usually be composed by a handful of simple ones. I have yet to see a system like this actually in practice, but I can imagine something like it could exist, given the appropriate tools (in particular, a good, state-of-the-art language that isn't some rehash of stuff from the 60s - Erlang might fit the bill).

The ideas of DO as articulated by Mike Acton are antithetical to the ideas of OO (although a well-designed future OO system - including late bound hardware - could probably represent and implement a lot of the techniques that Acton talks about to improve performance and quality). This doesn't mean they're wrong, but I see treating data as the _only_ thing that matters as being fundamentally opposed to the idea that data is actually the root of most evil. People adopting DO should be aware that robustly applying the technique in the long run will require having detailed knowledge of every aspect of the system on every change. (I think it's not a coincidence that it was originally developed for game dev, which often doesn't have the same long-term maintenance requirements as other kinds of software, and certainly not software like the Internet.)

FP and OO are more complementary. Immutability and referential transparency can be very valuable in object systems; encapsulation, extreme late binding and message passing can be very valuable in functional systems (in fact, Haskell and Erlang surpass "OO" languages when scored on some of these characteristics). Scala explicitly embraces the complementarity between the two paradigms.

[1] https://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_ka...

[2] https://www.quora.com/What-does-Alan-Kay-think-about-inherit...

[3] https://en.wikipedia.org/wiki/Flag_day_(computing)

[4] https://news.ycombinator.com/item?id=11945869

Re:Pretending OOP Never Happened by baryphonic on Saturday May 16, 2020 @01:14PM (#23205238)
Oof, I just looked at the formatting of this post and realized I rendered the list wrong. Here's the non-eyebleed version:

- Class inheritance is the biggest one. Kay himself mentions inheritance appearing only in the later versions of Xerox's Smalltalk. No need to really elaborate. Implement interfaces, compose behavior and don't inherit. He has posted some more of his thoughts on this exact topic.[2]

- Java was a giant industry-harming one. Now multiple generations of programmers have been brought up to think that the definition of an object is to write a class (which is not an essential characteristic), declare internal state and then write accessors and mutators for every one of them. We might as well write COBOL. But, having tried to solve problems in Java, I get why it's done. The OO is so weak and awful that I'd rather just crank out slightly-safer C in Java than jump through the horrific hoops. (Just as an aside, I find it sad that Sun had the teams that developed both Java and Self. Self used prototypes instead of classes, which influenced Javascript, but also had a killer JIT that made it super fast and a ground-breaking generational garbage collector. Sun ended up cannibalizing the Self team to assist with Java, and some of the features of the JVM were ports of work that had originally been done for the Self VM.)

- C++ was kind of a mistake, because it introduced the interminable "public" and "private" and now the endless litany of stupid access modifiers. They were needed because C++ had this need to be compatible with C and do dynamic dispatch and have some semblance of type safety (though C is only weakly typed) and have it run (and compile) before the heat death of the universe. C++ isn't as horrible as what came after it though IMO (i.e. Java).

- Xerox horribly mismanaging Smalltalk was another mistake. Xerox was smart enough to realize that Smalltalk was insanely valuable, but instead of realizing that it should be a loss leader/free drugs, they decided to lock it away in the high tower and then let everyone else pillage their research that could be commercialized (GUIs on the desktop, laser printers, Ethernet, among many others). This literally led Apple to develop Objective-C.

Re:Pretending OOP Never Happened by jcelerier on Friday May 15, 2020 @01:07PM (#23194560)
> Even if you look at well-designed non-OO code like, say the Linux kernel

but the linux kernel is definitely OO, except vtables are filled by hand instead than with the help of a compiler.

https://www.kernel.org/doc/html/v4.10/driver-api/infrastruct...

structs with data + function pointers are literally OOP objects. see e.g. https://lwn.net/Articles/444910/

> you'll see that there are object oriented concepts like information hiding and polymorphism all over the place; they just don't formalize them with the "class" or "private" keywords.

yes, that's still OO. python doesn't have a private keyword either and that does not make its classes not OO.

Re:Pretending OOP Never Happened by kragen on Saturday May 16, 2020 @12:13AM (#23200437)
Smalltalk doesn't have a private keyword, or private methods, and it's the language the term "object-oriented" was invented to describe.
Re:Pretending OOP Never Happened by barrkel on Friday May 15, 2020 @01:04PM (#23194521)
Procedural code is quite popular in Java as used with IoC.

Global variables are called "Singletons". DI injects a reference to all the globals you need to do your work, so you don't feel so grubby. Often DI injects some kind of accessor to the database, a giant store of global variables.

Procedures are called beans, but you can tell they're procedures because they have a single public entry point and are usually called SomeKindOfVerber with a main verb() or do() or execute() method, they have the procedures they call (sorry, I mean "collaborators") injected, and they themselves are injected to where they'll be called from. Their state is ephemeral, and only exists for the purpose of executing a single flow of logic (this is called "bean scope").

All you need to do to describe your code as object-oriented is convert your procedures into beans, and tada!

Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @01:52PM (#23195126)
> Procedural code is quite popular in Java as used with IoC.

Yep, you get all the downsides of coding in Basic with the bonus of the performance hit you take from all the reflective instantiation.

Re:Pretending OOP Never Happened by apta on Friday May 15, 2020 @04:18PM (#23196918)
You don't have to incur the cost of reflection. Dagger2 for instance, is a compile time DI framework. Micronaut is also compile time.
Re:Pretending OOP Never Happened by sidlls on Friday May 15, 2020 @09:43PM (#23199698)
DI frameworks only encourage bloat, e.g., 1-to-1 interface to concrete classes. You know, for "unit testability" or "just in case" we need to implement the interface with a different concrete class later.

They can help simplify what is essentially a problem of complexity that is self-induced by the original decision to use OOP. But it's rare that they don't also introduce tons of additional other complexity, e.g., at runtime. Debugging these applications is one of the most infuriating things I've ever done as a programmer.

Re:Pretending OOP Never Happened by apta on Saturday May 16, 2020 @04:21AM (#23201479)
What kind of bloat are you talking about here? Binary and/or runtime cost? In the case of Java/JVM (and I believe C#/.NET) the JIT is almost trivially able to inline monomorphic calls, meaning that effectively, these interface calls are free.

Secondly, you don't even need to use interfaces with one-off implementations if you don't want. You can inject your concrete implementation class instances directly. Using interfaces just makes it easier to swap implementations as needed. The unit testability argument is completely orthogonal (and also in the case of the JVM, you can mock classes as they're open by default, so again, you don't even need to use one-off interfaces).

And I don't see how this issue is limited to OOP. At the end of the day, you're going to have to hook up your components and object graph anyway, either manually or using some kind of DI, so the complexity is always there. Even if you don't write interfaces with single implementors, non-trivial programs quickly become extremely verbose to implement. Maybe something like Scala's `implicit` can be of help here.

> Debugging these applications is one of the most infuriating things I've ever done as a programmer.

Again, Dagger and Micronaut do DI at compile time using codegen, so nothing to debug at runtime.

Re:Pretending OOP Never Happened by sidlls on Saturday May 16, 2020 @11:14AM (#23204035)
Compile time DI doesn't mitigate huge stack traces or using unnecessary interfaces, which both contribute to bloat.

Non-trivial programs don't have to be verbose. They are generally made so by the addition of unnecessary "features" that, in turn, require even more additions "to reduce complexity."

Re:Pretending OOP Never Happened by apta on Saturday May 16, 2020 @12:11PM (#23204644)
As I mentioned, you don't have to use unnecessary interfaces with DI (compile time or runtime). You can directly inject your implementation classes. Secondly, huge stack traces are not related to DI either, they're an orthogonal issue.
Re:Pretending OOP Never Happened by hagy on Friday May 15, 2020 @01:33PM (#23194865)
I agree with a lot of this, although I wouldn't conflate DI references with global variables. They certainly allow for shared state between different components, but the great thing about DI is that you can always create new contexts with a separate set of shared state references.

I also agree that is kinda of funny to constantly so the Verber classes everywhere in Java, but it has at least improved over the last several years with the standardization around Function<T,R>, Supplier<T>, Consumer<T>, etc. Lambda syntax and method references also help. I'd admit the syntax for using a functional style in Java is still a little jenky, but its quite an improvement from where things were back in the day.

Re:Pretending OOP Never Happened by barrkel on Friday May 15, 2020 @05:37PM (#23197775)
I'm being tongue-in-cheek, of course.

The biggest upside I see for automated DI is to get back concision once you've parameterized your code by the code it depends on. Usually this parameterization is done for unit testing purposes.

Direct calls are hard to exclude or intercept for testing, so you call a method on an interface or a base class or an object instead, and now you need to be initialized with a reference which may be replaced for testing purposes. But the requirement for initialization remains in production, even when there is normally exactly one candidate and it might not even require state if it wasn't for all the other references it in turn needs to call.

The great increase in the burden of initialization creates demand for the IoC / DI container. And once you have a container framework which takes care of gluing objects together, why not let it stick transparent adapters (proxies) around the objects too? Proxies for per-request state, multi-threading, transaction management, caching, all that good Common Lisp advice of before, after and around.

The temptation for clever people can be hard to resist, and before you know it your local code has global side effects unknowable to people who don't have the full picture in their head.

I actually blame unit testing, particularly when combined with a static type system. Static binding, in particular: you could mock static calls if dynamic binding like Emacs Lisp was available!

Unit testing is great at the leaves of the call graph, for self-contained things like container libraries, or things which behave functionally, i.e. they transform things.

Unit testing is like arthritis in the middle of the call graph. It makes rearchitecting much harder by baking in assumptions both upstream and downstream, and the easy temptation of mocking usually overspecifies the interaction with collaborators. The parameterization demands DI which encourages too much cleverness, as discussed. And unit tests don't leave you very confident that things will hold together because test mocks and stubs don't necessarily replicate the behaviour of the real things.

I like integration testing higher up the stack, and trying to convert a program as much as possible into a composition of functional elements - things that belong on or near the leaves of the call graph, and leaving as little "middle" as possible to be infected with unit tests.

Re:Pretending OOP Never Happened by oneplane on Friday May 15, 2020 @05:00PM (#23197338)
I agree and might add that having improvements on something imperfect (but a thing that does get real work done) can be worth much more than compsci perfectionism.

It is very hard to do something that is 'good' in technical terms while also being 'good' in product terms. While the discussion often doesn't revolve around the practical implications of taking steps toward a commercially uncommon language or paradigm during business engineering it is pretty much where all the 'stuff gets done'.

Re:Pretending OOP Never Happened by josephg on Friday May 15, 2020 @06:48PM (#23198432)
You can have your cake and eat it too. For example, react’s functional components are both good in product terms and good in technical terms. C#, JavaScript, python, swift and others have lots of tools for programming in a more functional style.

And java isn’t where all the ‘stuff gets done’ - well, outside of the enterprise programming bubble. JavaScript and (surprisingly) python are the workhorses of the modern startup ecosystem. I can’t speak with certainty about python, but separating logic and data, and using immutability with stateless components and functions is all very common in the JavaScript world.

Re:Pretending OOP Never Happened by oneplane on Friday May 15, 2020 @08:08PM (#23199025)
I didn't intend to sound like I meant that Java is where the stuff gets done but rather that 'stuff gets done' using non-ideal languages, frameworks, tools, context etc.

C, C++, Java, Python, JavaScript, C#, even PHP are not exactly ideal, even in their special niches, but a lot of real world stuff, if not most 'stuff' gets done using that.

Re:Pretending OOP Never Happened by atoav on Friday May 15, 2020 @12:49PM (#23194296)
As somebody who tried to force OOP onto Rust only to realize how easy things can go when you use a more data-oriented/compositional approach: I think the most important thing to keep in mind is that OOP is only one possible way of abstracting things.

There are other ways as well and all of them work well for certain problems and not so well for others. Limiting yourself to only one lense with which you look at problems is a bad thing.

Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @01:01PM (#23194475)
> Limiting yourself to only one lens

Well, agree, but in my experience people who abandon OO are usually the ones who are limiting themselves to only one lens - the top-down/procedural way software was developed in the 60's and 70's which is easier to comprehend but ultimately unsustainable for subtle reasons. (not saying that's you, just saying a lot of people do).

Rejecting OO in favor of FP like Paul Graham does? Great! Hopefully you'll make some headway and functional languages/styles will really catch on!

Rejecting OO in favor of one big function that handles everything and a few global variables (like nearly everybody who uses the Spring "framework" abomination in Java)? Maybe spend some time considering why, if it's so obvious, it's referred to as a "bad practice".

Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @02:11PM (#23195369)
I think you and I must be part of different bubbles, because I haven't heard anyone advocating for moving away from OOP toward procedural code. Looking at new languages which are proudly not OO (e.g., Go, Rust, etc), none appear to be advertising that you can write code that looks like 60s/70s procedural code--or at least not in the "pervasive global state" sense that you originally attributed to it. Instead, those languages tend to have first class functions and discourage global state.
Re:Pretending OOP Never Happened by cy_hauser on Friday May 15, 2020 @03:07PM (#23196093)
> "... languages which are proudly not OO (e.g., Go, ...."

Outside of class based inheritance which aspects of Object Oriented is Go missing? I code Go daily and consider it primarily an OO language.

Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @04:20PM (#23196952)
In general, "OO design", which is to say if you're writing a BookStore application, there are no Book types with "sell()" methods that imply that book instances must have a reference to the payment service and everything else in the application in addition to their plain-old-data fields (note that this is another phrasing of the banana/gorilla/jungle quip). Inheritance and OO design are the distinguishing features of OOP in my mind; if you don't have these, then OOP is indistinguishable from FP or DOP. If Go is OOP, then which languages aren't, and why?
Re:Pretending OOP Never Happened by cy_hauser on Friday May 15, 2020 @05:41PM (#23197816)
> "Inheritance and OO design are the distinguishing features of OOP in my mind;"

I don't consider inheritance an important feature for a language to be an "Object Oriented Language." Inheritance: none, single, and multiple have been bandied about in the OO world for quite some time now so I understand where you're coming from.

I break out design from the language. While I wouldn't design very deep without knowing the language I was targeting I do consider Go as having a natural bias towards driving me to OO design over, say, functional or procedural. For me Go lends itself naturally to OO design. Sans inheritance, of course.

> "if you don't have these, then OOP is indistinguishable from FP or DOP. If Go is OOP, then which languages aren't, and why?"

Haskel, some Lisps, some Forths, etc. would be more non-OO languages in my mind. C is also a non-OO language to me even though you can certainly code it in an OO style.

I guess I agree with the blog author, OO is simply part of the language landscape these days and isn't going to be supplanted by functional any more than procedural was supplanted by OO. And with my mind-set then I'll say most all of Algol's recent children can be considered OO languages. So for me Go is definitely an object oriented language. But I now understand why you don't.

Re:Pretending OOP Never Happened by hnick on Friday May 15, 2020 @10:11PM (#23199866)
> I don't consider inheritance an important feature for a language to be an "Object Oriented Language."

I mostly agree except for one catch. How do you extend a class which is encapsulated properly? Often this is the only way I use inheritance because I need to do one or two extra things that rely on the internal state of an object which I can't modify directly. So I'm wondering if there is a good way that doesn't rely on modifying the implementation of the original object to be more flexible.

Re:Pretending OOP Never Happened by cy_hauser on Saturday May 16, 2020 @08:22AM (#23202811)
This is tough to answer without a language in mind. Depending on what access you have you can use interfaces to get run time method binding or composition to get compile time binding. For either of these you'll have to "wire" the needed types together. This is what Go has you doing.

Going back to the "old days" you'd reach for a pattern. Adapter pattern was the most common. Bridge or Facade maybe? But patterns fell out of favor a while back (imo) because they were tough on the coder's mind. A lot of languages added useful bits to create these pattern behaviors more naturally and without reaching for a book. I'd need to see code to be more specific.

Re:Pretending OOP Never Happened by hnick on Saturday May 16, 2020 @09:07AM (#23203078)
I'm not familiar with Go but apparently it has "embedding" which is a little different. Maybe that works. But it's largely playing the same role - a way to extend a class you might not control.

So I'd rephrase that I suppose, perhaps we don't need something called Inheritance, but we do need a way to extend objects we don't control without breaking encapsulation.

As an example if you had a method that outputs XML and you want JSON, you can use an adapter object to call it, read the string, and reformat and pass it through. That works (and I've done things just as bad or worse many times from necessity), but it's definitely more hackish and less efficient than a solution that uses object fields to output JSON natively. Inheritance is my familiar way to do this in cases where I don't control the code (in which case injecting a Writer class of some sort probably makes more sense).

Re:Pretending OOP Never Happened by cy_hauser on Saturday May 16, 2020 @11:19AM (#23204081)
Go's embedding _is_ composition (has-a) in any other OO language but with a little sugar. Embedding allows the compiler to optimize the memory layout for a composed object and allows you to reference the composed object's fields without a qualifier. For example, if a Student class has-a Address {city, state, zip} then you could refer to the city field by coding aStudent.city rather than aStudent.address.city. It's handy but doesn't otherwise change "normal" composition.

I'm not advocating against inheritance. If it works then go for it. I do when I'm coding in languages that have it. This stemmed from a thread discussing whether the lack of inheritance in Go was sufficient to exclude it from being an object-oriented language.

Re:Pretending OOP Never Happened by throwaway894345 on Saturday May 16, 2020 @05:06PM (#23207227)
> but we do need a way to extend objects we don't control without breaking encapsulation.

By definition, you can't extend objects you don't control without violating encapsulation. Encapsulation is all about controlling access so that the owner of a class can make changes without breaking downstream code. If downstream code accesses these private APIs, the downstream code may break, regardless of whether the access was via inheritance or composition (as a side note, "protected" makes no sense--who cares if downstream code breaks because it was accessed by inheritance but not by composition?).

> As an example if you had a method that outputs XML and you want JSON, you can use an adapter object to call it, read the string, and reformat and pass it through. That works (and I've done things just as bad or worse many times from necessity), but it's definitely more hackish and less efficient than a solution that uses object fields to output JSON natively. Inheritance is my familiar way to do this in cases where I don't control the code (in which case injecting a Writer class of some sort probably makes more sense).

You can do this just as easily without inheritance. You just need a way to access the member fields--whether you access them via inheritance or otherwise doesn't really matter. E.g.,

    class Foo:
        def __init__(self, x: int, y: int) -> None:
            self.x = x
            self.y = y

        def xml(self) -> str:
            return f"<foo x={self.x} y={self.y} />"

    # Via inheritance
    class JSONFoo(Foo):
        def json(self) -> str:
            return json.dumps({"x": self.x, "y": self.y})


    # Via composition
    class JSONFoo:
        def __init__(self, foo: Foo) -> None:
            self.foo = foo

        def xml(self) -> str:
            return self.foo.xml()

        def json(self) -> str:
            return json.dumps({"x": self.foo.x, "y": self.foo.y})

    # Simple
    def json_foo(foo: Foo) -> str:
        return json.dumps({"x": foo.x, "y": foo.y})
> I'm not familiar with Go but apparently it has "embedding" which is a little different. Maybe that works. But it's largely playing the same role - a way to extend a class you might not control.

Go's embedding is exactly composition. It doesn't let you access private member data of the embedded class. It's just syntax sugar for delegation. In other words, you could create the composition version of the JSONFoo class like this:

    type Foo struct { X: int; Y: int }

    func (f *Foo) XML() string { return fmt.Sprintf("<foo x=%d y=%d />", f.X, f.Y) }

    // The compiler will automatically generate this method:
    //
    // func (jf *JSONFoo) XML() string { return jf.Foo.XML() }
    //
    // And for a given instance of JSONFoo, the compiler will convert all instances
    // of jsonFooInstance.X to jsonFooInstance.Foo.X.
    type JSONFoo struct {Foo}

    func (jf *JSONFoo) JSON() string { return fmt.Sprintf(`{"x": %d, "y": %d}`, jf.X, jf.Y) }
Note that a `JSONFoo` isn't a `Foo`. This is an error:

    func printXML(foo *Foo) { fmt.Println(foo.XML()) }

    func main() {
        jsonFooInstance := JSONFoo{0, 0}
        // printXML(jsonFooInstance) // error
        printXML(jsonFooInstance.Foo) // correct!
    }
Re:Pretending OOP Never Happened by hnick on Saturday May 16, 2020 @09:11PM (#23208553)
Thank you for the examples. I've been in perl too long where a lot of good practice is just a suggestion and not enforced. In particular private vs protected (which is what I was thinking of regarding accessing internals) doesn't really exist.

I tend to lean towards inheritance for functional extensions and composition for everything else, but there are so many ways to skin an OO cat.

Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @08:25PM (#23199141)
I'll be blunt: That Book type design is horrible, precisely because of the problem you are complaining about. The answer is simple: Don't do it that way.

If that's what people think OOP is supposed to look like, no wonder they don't like it.

Re:Pretending OOP Never Happened by temac on Friday May 15, 2020 @09:17PM (#23199524)
I suspect "OOP" is teached like that most of the time. Or more precisely, this is teached as being OOP (as a fundamental distinguishing point from "procedural", most often -- ironically, the opposition "OOP" / "procedural" is then also complete garbage)

I've yet to find a non boring, yet descriptive, consensual alternate definition of OOP; the first thought people have is more Java than Smalltalk, and if you exclude inheritance and everything virtual, then it boils down to syntactic sugar for single non-dynamic dispatch, and encapsulation. On the other (but still very sweet) hand, is Ruby OOP when you write 10.times { puts "hello" }? I don't know. Or rather: it's completely arbitrary. Even what is the most useful, when used cleanly is not exclusive of "OOP": invariants are also most important, and arguably way more well known by practitioners, in FP.

Natural language is inherently descriptive. If there is a better "OOP", you just have to fight to make it prevail, so that people think of it when they hear "OOP". Maybe we can make the bad teachers stop in other ways too. Like not even using the word, and making "alternate" approaches (maybe even what you are calling OOP) fashionable.

Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @09:49PM (#23199740)
I think of OOP as encapsulation, inheritance, and polymorphism (but not necessarily all of them being used all of the time).

My problem with the example is that Book should not have a sell() method. Instead, BookStore should have a sell(Book) method. The machinery for how to send in a credit card transaction does not belong in Book. You still need it - it has to live somewhere - but BookStore is the place, not Book.

Re:Pretending OOP Never Happened by jholman on Saturday May 16, 2020 @07:06AM (#23202439)
> OOP as encapsulation, inheritance, and polymorphism

Ugh.

Polymorphism is far from unique to OO, and predates it.

If by encapsulation you mean information hiding, that's as old as the birth of OO. I've also sometimes encountered an alternate definition of encapsulation that more or less means "information hiding in OO", which seems a bit circular to me?

That doesn't mean OO can't have those features, but you didn't say "OOP usually includes loops and conditionals", because it's too obvious to mention.

Re:Pretending OOP Never Happened by dingoegret on Saturday May 16, 2020 @01:09AM (#23200660)
Exactly this. As someone who abandoned OOP, I find that my programming has improved a lot. But whenever I hear criticism of OO, it's always about this ancient taxonomic structuring (is-a has-a) idea that no one doing post 90s OOP is doing at all. And it's always proponents of "simpler" procedural paradigms that harp on about this. Weird.
Re:Pretending OOP Never Happened by throwaway894345 on Saturday May 16, 2020 @11:47AM (#23204371)
If no one does this taxonomic structuring or god object design, then what distinguishes good OOP from data oriented programming? Also, taxonomic design was popular in code and in pedagogy (in Java, C#, and C++ anyway) when I was in school circa 2010.
Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @09:48PM (#23199734)
That’s pretty much my point. If you strip this away, what is left to distinguish it from other paradigms? Inheritance?
Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @10:47PM (#23200044)
See my reply to tenac. The problem isn't that OOP is bad, the problem is that you're doing it badly. It's not that you misunderstand what the ideas are, either. It's just... really bad design.

You even know what the problems are with the design - you described them quite well. But it's not showing that OOP is horribly flawed. It's just showing you that you need a different design.

Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @11:56PM (#23200364)
I’m pretty convinced that it’s bad design, but many OOP practitioners (including textbooks and thought leaders for a good while) disagree. And even if you agree that all of these facets of bad design (including inheritance hierarchies) are Not True OO, then what is left that distinguishes object- from data-oriented design? What value does “OO” add to our lexicon?
Re:Pretending OOP Never Happened by AnimalMuppet on Saturday May 16, 2020 @04:58PM (#23207159)
I didn't say that inheritance hierarchies are bad. They're great... if you've got entities that are actually in a hierarchy. I've used a hierarchy in a genetic programming situation, with a Statement being the base class, and a number of derived classes that implemented the available instruction set, with a virtual execute() method. It worked great... for that problem.

If you're having a hierarchy just to have a hierarchy, that's about as wise as having gotos just to have gotos. Nobody sane would do that today; maybe we'll get there with hierarchy, too.

I'm not sure what your definition of "data-oriented design" is, so it's hard for me to say how OO is different. I'll take a stab at it anyway, but know in advance that my response may be orthogonal to your question.

You've got data - say, data about a book, in the original example. In the structured programming days, that would be an "entity"; now it's an "object". The difference is that, with private methods, nobody can modify the book's data just by having a reference or a pointer to it. (You could kind of do this in C with a source file that would operate on the structure, and some of the methods being file static. But that doesn't keep any other code that has a .h file from modifying the structure without using the functions.)

The philosophical difference with OO might be that OO uses the compiler to enforce that data is only accessed in approved ways. This is a logical extension of static typing. (Of course, for non-static-typed OO languages, it doesn't happen that way. They enforce it at runtime.)

Re:Pretending OOP Never Happened by throwaway894345 on Monday May 18, 2020 @02:21PM (#23226346)
Sounds like OO to you is all about encapsulation. I think that's fine, but lots of paradigms support encapsulation. It's absolutely pervasive in FP and DO programs, for example (even C supports opaque pointers). Do we say any language with encapsulation is also object-oriented?

Or is OO encapsulation + inheritance? One problem is that the OOP community seems split about whether or not inheritance is a defining feature of OOP. And since encapsulation isn't a defining feature, surely it's inheritance. Unfortunately, there's no clear consensus, so OO as a term is not very useful.

> The philosophical difference with OO might be that OO uses the compiler to enforce that data is only accessed in approved ways. This is a logical extension of static typing. (Of course, for non-static-typed OO languages, it doesn't happen that way. They enforce it at runtime.)

This is an interesting distinction; ironically C compilers support encapsulation via opaque pointers while Python doesn't enforce encapsulation even at runtime (it's all based on convention--"consenting adults" and all that).

> I didn't say that inheritance hierarchies are bad. They're great... if you've got entities that are actually in a hierarchy.

I think there are cases where inheritance doesn't overtly bite you, but I've never seen a case where inheritance is actually cleaner than composition (with the exception that in many languages, inheritance is the only way to automatically delegate--they lack something like Go's struct embedding). Specifically in your Statement example, you could have gotten your polymorphism from a Statement interface instead of a base class.

Inheritance seems like just a particular subset of composition and polymorphism--there are cases for which it is appropriate, but why does it need to exist at all when you can get the same benefits from composing the constituent components (components being polymorphism and composition via interfaces/first-class-functions and object-/functional-composition, respectively)? It feels like having an add4() function--it's not always bad (sometimes you really need to add 4 to another number), but the more general 2-argument add() function works just as well in the cases where add4() doesn't overtly bite you, and it's much less likely to be abused.

Re:Pretending OOP Never Happened by dingoegret on Saturday May 16, 2020 @01:15AM (#23200693)
The problem with your claims now is that they are unfalsifiable. How do we go about proving or rejecting your ostensible claim that this how OOP programmers say it should be done? The OOP community has a vibrant culture of critiquing bad designs. See DDD and similar efforts.
Re:Pretending OOP Never Happened by throwaway894345 on Saturday May 16, 2020 @11:45AM (#23204346)
> How do we go about proving or rejecting your ostensible claim that this how OOP programmers say it should be done?

Off the top of my head you could survey the landscape of OOP books, blog posts, and code bases throughout history. But it doesn’t really matter—even if you don’t believe me and all OOP programmers believe these are design flaws, then my question remains: what distinguishes “good OO design” from data oriented design?

Re:Pretending OOP Never Happened by SPBS on Friday May 15, 2020 @09:33PM (#23199632)
If Go's paradigm is considered OO then what would you consider non OO? Seems like almost every language has the baseline of struct and struct methods (with private and public semantics)
Re:Pretending OOP Never Happened by dragonwriter on Friday May 15, 2020 @09:38PM (#23199661)
> If Go's paradigm is considered OO then what would you consider non OO?

Classic, pre-object Pascal. C. Very little new in the last few decades, because OOP is a very influential paradigm.

Re:Pretending OOP Never Happened by hnick on Friday May 15, 2020 @10:04PM (#23199823)
Even though it's not OO that doesn't stop people adding their own hacked OO to C. I think the Quake source code had a lot of this, structs with function pointers. OO is such a natural fit for games in particular it finds a way to exist.
Re:Pretending OOP Never Happened by throwaway894345 on Friday May 15, 2020 @10:28PM (#23199952)
The whole GObject ecosystem is hacky object oriented C, to provide another suite of examples.
Re:Pretending OOP Never Happened by hnick on Friday May 15, 2020 @11:13PM (#23200168)
I personally found hacked OO ala Perl to be the first time I really understood it, even after years of class diagrams at university. The abstraction got in the way. I needed the nuts and bolts.
Re:Pretending OOP Never Happened by throwaway894345 on Saturday May 16, 2020 @11:51AM (#23204402)
Yeah, I delved into it when I was working with C, but by that time I had no interest in reproducing inheritance but only interfaces.
Re:Pretending OOP Never Happened by debug-desperado on Friday May 15, 2020 @01:30PM (#23194827)
Without DI frameworks and IoC class design, good luck testing your OO code.

I also don't get the Spring hate and what that has to do with creating "one big function and a few global variables."

Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @01:46PM (#23195028)
Dependency Injection and Inversion of Control are great design principles that most capable programmers discovered on their own before they were given names. Having a worthless container instantiate everything via reflection and moving what ought to be compile-time errors into the runtime to enforce that is an unnecessary waste.
Re:Pretending OOP Never Happened by debug-desperado on Friday May 15, 2020 @02:40PM (#23195721)
Taking care of instantiation AND teardown saves devs from doing quite a lot of work.

Do you use Dagger for your DI needs or do you roll your own container?

Re:Pretending OOP Never Happened by iainmerrick on Friday May 15, 2020 @06:56PM (#23198476)
Instantiation and teardown aren’t hard, or at any rate they shouldn’t have to be. If you roll this stuff by hand, it can often indicate weak areas of the design -- unnecessarily tedious boilerplate that can be cleaned up.

To me, DI frameworks are usually an example of the tail wagging the dog. You make your code more complex, slower and harder to debug just for the sake of making it more testable. But if you avoid leaning too hard on the framework, it’s usually possible to keep the design clean and get good test coverage. In fact you can often get better test coverage, by using real components rather than being tempted into relying on mocks.

Re:Pretending OOP Never Happened by marcodave on Friday May 15, 2020 @02:01PM (#23195236)
...and some of those Most Capable Programmers helped by letting other reuse what they wrote, in form of IoC/DI frameworks! Or are you suggesting that every programmer worth their salt should write their own IoC to suit their needs?
Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @02:27PM (#23195579)
See, that's kind of like saying, "the Most Capable Programmers helped by writing a program, are you suggesting that every programmer should write their own program to suit their needs?" I mean, yes... absolutely. I don't think anybody should write a program that parses an XML file, reads class names out of specific attributes, reflectively instantiates them, then reflectively matches up their methods with other class names and then reflectively associates them, not do I think that anybody should write a program that scans annotations to match class types to attributes and instantiate them behind the scenes either. I don't think Rod Johnson should have done that, and I don't think anybody should use it. It's pure overhead with no benefit. I do think that programmers should decouple implementation from interface and program to interfaces and then write a dozen lines of Java code that do, in a type-safe, efficient way what the eight or ten megabytes of Spring "framework" does a bad job of doing "for you" slowly and poorly.
Re:Pretending OOP Never Happened by hodgesrm on Friday May 15, 2020 @05:31PM (#23197714)
Actually that's what I typically do in Java. There's far too much magic in Spring. Initialization code to read a parameter file and inject values into objects only takes a couple hundred lines. Plus there's huge value in controlling boot and exit sequences directly.

I do the same with object-relational mapping--write a thin layer of classes to wrap INSERT, SELECT, UPDATE, DELETE. Not hard to write, easy to test, and you can always slip into SQL if you run into problems.

Use-case specific code should always be considered as an alternative to general frameworks.

Re:Pretending OOP Never Happened by atoav on Saturday May 16, 2020 @05:45AM (#23202005)
That is not what I meant. Learning Rust was the best thing that ever happened to my Python Code for example. Does this mean I never use OOP patterns anymore? Not at all.

I just think more about hoe best to structure my code. Sometimes a OOP-like pattern works well, sometimes something else works better.

Re:Pretending OOP Never Happened by jchook on Friday May 15, 2020 @01:07PM (#23194552)
Rust’s type + interface system seems to completely fix the problems of oop traits & hierarchical inheritance.
Re:Pretending OOP Never Happened by _0ffh on Friday May 15, 2020 @01:52PM (#23195117)
>object oriented concepts like information hiding

Having read "Software Fundamentals" [1], I think David Parnas [2] would beg to differ on that bit. Not the least because he actually invented the concept.

Parnas wrote extensively on information hiding as the basis for modular program construction, abstract interfaces that provide services without revealing implementation, and other software engineering topics.

[1] https://www.goodreads.com/book/show/1416932.Software_Fundame...

[2] https://en.wikipedia.org/wiki/David_Parnas

Re:Pretending OOP Never Happened by cy_hauser on Friday May 15, 2020 @03:29PM (#23196358)
Not sure what you're saying. Are you saying OOP does not have information hiding or that the "information hiding" in OO languages should not be called information hiding as per Parnas? Or something else entirely?
Re:Pretending OOP Never Happened by _0ffh on Friday May 15, 2020 @06:29PM (#23198276)
I'm saying that just because someone learned about a concept (in this case, information hiding) in a certain context (OOP), that does not mean that this is where that concept originated (which was implied). It's a common mistake I see all the time.

In short, information hiding is a concept from modular procedural programming that was then adopted by OOP, not an original OOP concept.

In general, OOP is really mostly a case of "let's take a bunch of known best practices and encourage (or even enforce) them on the language level". Procedural programming can be as object-oriented (or not) as you like. With minimal amounts of syntactic sugar in the compiler, like e.g. rewriting "foo.bar(...)" to "bar(foo,...)" you won't even be able to easily tell the difference when you see the source code.

While I'm at it, the critique that procedural programming necessarily results in a quagmire of global state is also wrong. It's all up to the programmer. Nobody forces you to use globals. Some OO languages try to force you not to, but usually fail in the face of determined opposition from the coder.

In essence, foo coders will find a way to produce foo code.

Re:Pretending OOP Never Happened by cy_hauser on Friday May 15, 2020 @06:45PM (#23198398)
Got it. Makes sense.
Re:Pretending OOP Never Happened by revertts on Friday May 15, 2020 @06:11PM (#23198125)
I read their point as “information hiding is not a concept exclusive to OO.”

Whether or not the original quote meant to imply that I’m not sure, but do see how it could be read that way.

Re:Pretending OOP Never Happened by namelosw on Friday May 15, 2020 @11:21PM (#23200219)
Plain global state is not that bad actually. What people do to fix the global state made things worse, and IMO OO is also a terrible attempt.

Mutate coarse-grained or global state is pretty much similar to functional programming approaches. The global state is a big tree, and there are functions designed for this type, there are also functions and types for every fraction of the tree. It's straight forward, elegant, and extensible.

It's basically what Alan Perlis said in SICP: "It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures."

The only difference is just functional programming always transact the whole tree instead of mutating it, since they use immutable algebra data type.

It's not far from what Redux/Erlang process/Haskell ST Monad etc does.

IMO OO makes it more obscure and too subjective compared to this approach. It makes people lost the whole picture very quickly, and then accidental complexity comes in play in every corner.

Re:Pretending OOP Never Happened by bernawil on Friday May 15, 2020 @11:57PM (#23200368)
bingo. It's not "global state" what's wrong. It's global MUTABLE state.

OOP brought the terrible idea of coupling state and behavior, so of course "global state" would be wrong.

Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @12:58PM (#23194429)
OOP had another advantage over procedural: Often in procedural programming, you'd pass around a pointer to some data structure. If the data structure got messed up (put in an inconsistent state, say), you had no idea what code was responsible. With OOP, you look at the class's member functions, which is a much smaller set.
Re:Pretending OOP Never Happened by zozbot234 on Friday May 15, 2020 @01:05PM (#23194535)
> If the data structure got messed up (put in an inconsistent state, say), you had no idea what code was responsible. With OOP, you look at the class's member functions, which is a much smaller set.

Yes this is correct. But data abstraction and encapsulation are found in all object-based code; they're not specific or related to OOP.

Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @01:26PM (#23194784)
I'm not sure I understand. In your view, what is the difference between "object-based code" and OOP?
Re:Pretending OOP Never Happened by kingdomcome50 on Saturday May 16, 2020 @12:20AM (#23200465)
Simply using objects in the code doesn't mean one is practicing OOP any more than using functions means one is practicing FP.

That is, it is more about the "how" than the "what".

Re:Pretending OOP Never Happened by beeforpork on Friday May 15, 2020 @01:36PM (#23194912)
How is there a smaller set because of OOP? Just filter the functions by parameter type to restrict which function may access your pointer.
Re:Pretending OOP Never Happened by Jtsummers on Friday May 15, 2020 @01:48PM (#23195054)
OOP encourages modifying an object's state via its member functions (if you use data hiding/encapsulation as part of your OOP definition). This means that users of class instances shouldn't be able to put it into a bad state, unless the member functions are able to put it in a bad state.

This lets you isolate where your contract/invariant violations are more easily. Once addressed in the member functions, either it fixes it in the callers, or their use is now invalid and becomes a compile or runtime error.

Re:Pretending OOP Never Happened by Konohamaru on Friday May 22, 2020 @02:21AM (#23269183)
It seems like there is a whole subject buried in this comment.
Re:Pretending OOP Never Happened by overgard on Friday May 15, 2020 @12:35PM (#23194122)
Procedural programming was never abandoned, it just doesn't have a hype machine around it. How much software is written in plain C which is neither OO nor functional?
Re:Pretending OOP Never Happened by commandlinefan on Friday May 15, 2020 @12:47PM (#23194271)
> How much software is written in plain C which is neither OO

And I would assert that every maintainable C program is fundamentally designed in a relatively object-oriented way (functions that operated on structured, polymorphic function pointers, etc.) even if the original author wasn't particularly thinking in terms of classes or inheritance.

C programs aren't functional because C isn't functional (no inherent concept of closures), but Greenspun's tenth rule: Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp" usually applies.

Re:Pretending OOP Never Happened by phkahler on Friday May 15, 2020 @01:10PM (#23194587)
>> C programs aren't functional because C isn't functional (no inherent concept of closures)

You don't need closures to do functional programming.

I was recently tinkering in some C++ code and thought "this part should be able to run in parallel." It was simply doing the same operation of a list of objects. It's a complex operation involving some temporary data structures. I had to dig through several layers of functions, but I kept finding that all data was created locally. Functions that returned complex result are passed a class to fill out. It was then that I realized it was written in a functional style - or at least my limited understanding of what that means. The top level function is now run in parallel, which required no changes beneath that level.

Now I prefer that style but like TFA here, I don't think 100 percent functional is a good idea.

Re:Pretending OOP Never Happened by overgard on Friday May 15, 2020 @01:41PM (#23194970)
I think what you're illustrating there is how vague of a term OO is. If you can apply it to code that was never even intentionally written to be OO then is it that meaningful? Btw OO did not invent encapsulation and polymorphism.
Re:Pretending OOP Never Happened by btilly on Friday May 15, 2020 @01:20PM (#23194725)
And I would assert that every maintainable C program is fundamentally designed in a relatively object-oriented way (functions that operated on structured, polymorphic function pointers, etc.) even if the original author wasn't particularly thinking in terms of classes or inheritance.

That is in the eye of the beholder.

I can well believe that you would look at the code and decide that the author wrote in an OO way. I also believe that there are plenty of authors of such code who would disagree with your assessment.

Re:Pretending OOP Never Happened by oivey on Friday May 15, 2020 @01:04PM (#23194514)
I don’t think it is correct to say that those things are OO because they predate the concept and are used in many paradigms. When I think of OO I think of strict encapsulation (via private), complex inheritance, methods bound and defined in classes, single dispatch, etc.
Re:Pretending OOP Never Happened by ferzul on Saturday May 16, 2020 @01:15PM (#23205250)
so languages like self and javascript which, even in the modern forms, have no real classes are not oo to you?

i mean, the fact that you can use class style syntax do define object constructors in javascript and tho result is fairly coherent, seems to suggest that classes are not inherent to oo.

also, the fact that it's called “object oriented” not “classical”.

other examples of non-oo with strict encapsulation includes haskell, where if you lack the constructor, you can only use it according to the public api. it also uses single dispatch for typeclasses.

complex inheritance rare in oo - mostly only single inheritance is supported - and most oo design advocates pretty much say “don't use inheritance”. so it doesn't seem like it's intrinsic.

Re:Pretending OOP Never Happened by pmarreck on Friday May 15, 2020 @12:42PM (#23194206)
It is perfectly possible to use a functional style in an OO/procedural language, as John Carmack demonstrates below. You just lose the guarantees (which is a steep cost IMHO)

https://www.gamasutra.com/view/news/169296/Indepth_Functiona...

Re:Pretending OOP Never Happened by zackees on Friday May 15, 2020 @12:51PM (#23194334)
One of the main reasons why OOP is popular is not even talked about - the ability for AUTOCOMPLETE in the IDE to work extremely well.

For example, I have an object... what do I do with it? So I hit the '.' on my keyboard and read the list of things that the object can do.

This is why OOP is popular, it allows better tooling. If people want to move beyond OOP then they need to think about how the new style integrates with the IDE so that the programmer can hit the '.' or equivalent and get a list of useful things to do with the object in question.

Re:Pretending OOP Never Happened by kqr on Friday May 15, 2020 @01:13PM (#23194633)
This is a common idea but I'm skeptical it's that simple.

Haskell has a powerful enough type system that it can enable "hole-driven development" where you specify the type of your code and then leave a "hole" in the implementation -- anywhere in the implementation, including making the entire implementation a hole! -- and the tools can suggest what should go in the hole, leaving new holes in the code.

Then you can incrementally resolve smaller and smaller holes based on the suggestions you get, and finally you have a completely tool-written program, where you just helped disambiguate when you had to.

This is the '.' on steroids.

Haskell is not popular.

Re:Pretending OOP Never Happened by vkou on Friday May 15, 2020 @01:24PM (#23194763)
Haskell is weighed down with the problem that by the time I'm five pages into a "Haskell is actually easy, and let me show you how" tutorial, the average reader's eyes glaze over thanks to passages like:

"While >>= and return are the basic monadic sequencing operations, we also need some monadic primitives. A monadic primitive is simply an operation that uses the insides of the monad abstraction and taps into the `wheels and gears' that make the monad work. For example, in the IO monad, operators such as putChar are primitive since they deal with the inner workings of the IO monad. Similarly, our state monad uses two primitives: readSM and updateSM. Note that these depend on the inner structure of the monad..."

Or, possibly because when I make a post on Hacker News about how difficult it is to implement Quicksort in Haskell, the first response is a four-line Haskell implementation of Quicksort, spawning a 50-post subthread which points out that it's not actually an implementation of Quicksort, because it does not have the same performance characteristics as Quicksort, and instead has the performance characteristics of a lethargic water buffalo.

By the end of the thread, the question of whether it is possible to write a Quicksort in Haskell that has the performance characteristics of Quicksort is left as an open research question.

Re:Pretending OOP Never Happened by microtherion on Friday May 15, 2020 @05:19PM (#23197561)
Instead of downvoting the parent, Haskell devotees would do well to recognize that this is a pretty good representation of the hurdle that would-be recruits to Haskell face.
Re:Pretending OOP Never Happened by kv_hrishikesh on Saturday May 16, 2020 @03:21AM (#23201180)
There is a reason why Haskell is not popular. But I don't think the impenetrable passages about Monads are the reason behind it. It is more about what you gain by adopting Haskell and powering through the impenetrable passages. If it is not blazing fast runtime performance and fast speed of development and maintainable code, then Haskell is pointless. As such, if there are those kind of benefits available, there is plenty of extra brainpower available among developers to comprehend passages.
Re:Pretending OOP Never Happened by zozbot234 on Friday May 15, 2020 @01:32PM (#23194857)
Agda literally has this "hole-driven development" as a standard feature in its IDE modes. Not only that, but when developing Agda code you get a list of every binding in scope, as well as a prompt of the type that must be provided to resolve the "hole". The expectation is that every part of developing Agda code can be expressed as a kind of "autocomplete", if desired.
Re:Pretending OOP Never Happened by hota_mazi on Friday May 15, 2020 @01:38PM (#23194940)
> This is the '.' on steroids.

Not really. This is all still painfully manual.

Both type hole development and auto completion are enabled by static types. Haskell could theoretically benefit from all these advantages that mainstream statically typed enjoy thanks to IDEA, Visual Studio, or XCode but the Haskell community has this arrogant attitude that they are above such "pragmatic" tools and as such, never took the importance of IDE's seriously.

So when you watch a video on Haskell's awesome type hole development, the developer is often using vim and typing everything by hand. In 2020.

Re:Pretending OOP Never Happened by dunefox on Friday May 15, 2020 @01:52PM (#23195124)
Except VS Code supports Haskell pretty well. And implementing IDEs is hard work, why do that when it's such a niche language that editors like VS Code suffice?
Re:Pretending OOP Never Happened by lalaithion on Friday May 15, 2020 @10:45PM (#23200034)
Re:Pretending OOP Never Happened by Der_Einzige on Friday May 15, 2020 @01:28PM (#23194809)
Haskell is popular in universities, fintech companies, and among the FP crowed at large. Many programmers know of it's benefits and simply avoid it because FP is very difficult for many to code in and Haskell is trying to be FP only...
Re:Pretending OOP Never Happened by spekcular on Friday May 15, 2020 @05:36PM (#23197766)
Popular in which fintech companies? I don't believe this is true, but I would be happy to wrong.

Jane Street uses OCaml, but of course that's not Haskell. All the other finance firms seem to use pretty standard C/C++ because they need the low latency.

Re:Pretending OOP Never Happened by brobinson on Friday May 15, 2020 @09:18PM (#23199531)
Standard Chartered uses their own dialect of Haskell. There's some talks on youtube about it.
Re:Pretending OOP Never Happened by antonvs on Saturday May 16, 2020 @12:29AM (#23200498)
I've done Haskell contract work at multiple large Wall Street investment banks. It's not like it's their only language - these are large orgs with lots of smart people, sometimes some of them use Haskell.
Re:Pretending OOP Never Happened by dunefox on Friday May 15, 2020 @01:49PM (#23195065)
> One of the main reasons why OOP is popular is not even talked about - the ability for AUTOCOMPLETE in the IDE to work extremely well.

That's just dot-syntax and modules. That has nothing to do with OOP itself. Nothing is keeping you from doing that in functional languages that support proper modules.

Re:Pretending OOP Never Happened by overgard on Friday May 15, 2020 @03:19PM (#23196227)
Well generally speaking you're just describing a search problem: I have this data, what operations can I perform on it? Hypothetically you could do something like if I type a keycode after a variable it lists functions that can be run on it. Its just a tooling thing not really a language design thing.
Re:Pretending OOP Never Happened by iainmerrick on Friday May 15, 2020 @07:04PM (#23198535)
But their point is -- and I agree! -- that with unfashionable OO languages there’s nothing hypothetical about it, it’s completely mainstream. All the IDEs have done this for years and it works extremely well.
Re:Pretending OOP Never Happened by simongray on Friday May 15, 2020 @05:10PM (#23197455)
> I have an object... what do I do with it? So I hit the '.' on my keyboard and read the list of things that the object can do.

As a counterpoint (relevant Rich Hickey rant): https://www.youtube.com/watch?v=aSEQfqNYNAc

Re:Pretending OOP Never Happened by oftenwrong on Friday May 15, 2020 @11:26PM (#23200242)
The poor design of that class aside, I would rather have the specific version rather than the unspecified version. If I have a HttpServletRequest somewhere in my code, I have a good idea of its structure just by knowing its type. The same cannot be said of a blob of maps and lists.
Re:Pretending OOP Never Happened by simongray on Saturday May 16, 2020 @08:58AM (#23203016)
> If I have a HttpServletRequest somewhere in my code, I have a good idea of its structure just by knowing its type. The same cannot be said of a blob of maps and lists.

Non-trivial data structures are always conformed to some specification, doesn't matter if its Clojure or an OOP-language. If you receive an HTTP request over the wire you will have to check it. Once you conform it to the specification you know exactly how it looks and there's no need to jump through hoops to access the information.

Re:Pretending OOP Never Happened by kqr on Saturday May 16, 2020 @01:04PM (#23205155)
But with proper static typing you don't have to check it because you already know it's correct since it's construction. It relieves a lot of mental burden for me to not even have to consider the case that it might be incorrect, as long as I'm dealing with structures internal to my program.

I have a lot of respect for Rich Hickey and I feel like he Understands something that goes straight over my head, but his appreciation for generic, shapeless maps and lists is something I just don't get.

Re:Pretending OOP Never Happened by simongray on Saturday May 16, 2020 @07:45PM (#23208160)
> But with proper static typing you don't have to check it because you already know it's correct since it's construction.

Its construction is dependent on it conforming to a specific form. In this case, your statically typed data comes from a parsed string that is then conformed to a specification to ensure that is has a certain shape. In Clojure you would just do the conforming to specification part and keep the data generic.

I think the appreciation for generic data has to do with the fact the fact that it makes Clojure code very generic and facilitates a lot of functional composition.

Re:Pretending OOP Never Happened by onemoresoop on Friday May 15, 2020 @09:47PM (#23199724)
I have a counterpoint. OOP made simple things way too complicated and that resulted in more jobs in software development. Its the sad truth. Or the happy one, depending on which side of the fence one is.
Re:Pretending OOP Never Happened by bklaasen on Friday May 15, 2020 @05:13PM (#23197488)
I think a mention of Gary Bernhardt's "functional core, imperative shell" talk from 2012 is worthwhile here.

My mind was blown when I first saw it. https://www.destroyallsoftware.com/screencasts/catalog/funct...

Re:Pretending OOP Never Happened by scottmp10 on Friday May 15, 2020 @10:26PM (#23199936)
I was introduced through his "Boundaries" talk [1] and had a very similar experience. I think a lot of programmers develop this methodology without really formalizing it. But practicing it consistently creates really nice code and easy unit testing. The one possible downside is long argument lists to functions.

[1] https://www.destroyallsoftware.com/talks/boundaries

Re:Pretending OOP Never Happened by golergka on Friday May 15, 2020 @05:57PM (#23197981)
> Usually when I see somebody arguing against object-oriented programming, I don't see them arguing for functional programming, but instead arguing for procedural (like Cobol, Basic, or Pascal) programming.

That's not what I'm seeing. The usual discourse around OOP is that some parts of it are bad. In particular, overriding concrete methods, and in general Java-like inheritance brings a lot of pain. "Everything is an object" from C# and Java also gets a lot of well-deserved criticism (https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...).

Other parts, like polymorphism, incapsulation, binding data with behaviour - I don't see that much criticism against this. For me, modern languages like Go or Rust (yes, I know it's almost a meme at this point, but still) bring sane OOP approach: while not being strictly OOP, they take the parts that work, and remove the parts that don't, while at the same time adopting many practices and abstractions from functional languages that proved useful.

Re:Pretending OOP Never Happened by pacala on Friday May 15, 2020 @07:58PM (#23198969)
There are 4 mainstream programming styles:

* Data Oriented Programming. There is data, and there are functions operating on the data.

* Functional Programming. Variant of DOP, with a strong emphasis on purism and abstract algebra concepts.

* Procedural Programming. Variant of DOP, lacking language level mechanisms or best practices around polymorphic interfaces. Largely obsolete.

* Object Oriented Programming. Everything is an object.

Information hiding and polymorphism are readily available in all styles, except PP. The trouble with OOP is that insists on two big lies:

* Everything is an object, therefore data is an object, therefore data must be hidden. In fact, data simply 'is' and you can't hide it. Think JSON. There is nothing to hide.

* Everything is a polymorphic object, codified as the popular 'Open/Closed Principle'. In fact, most code is functions that just compute some data. Never needs N different polymorphic variants. If the business cases ever warrant such variants, simply refactor a polymorphic interface into the code base.

Re:Pretending OOP Never Happened by dingoegret on Saturday May 16, 2020 @01:59AM (#23200839)
I don't see any difference between DOP and procedural. Saying the difference is best practices and something vague about language mechanisms is accute to saying procedural is different to DOP because people say "do it good". This is even more formless than the criticism of no hard definitions levied against OOP by the same people.
Re:Pretending OOP Never Happened by pacala on Saturday May 16, 2020 @02:47AM (#23201044)
The technical difference is lack of language-level module support, requiring manual management of vtables. It can be done, see Linux kernel for an example, but often times degenerates in a ball of globals.

To further clarify, one can use DOP style in many languages, including languages that are ostensibly OO. DOP is a style, using data hiding and polymorphism as sparingly as possible, as opposed to OOP style, which strongly encourages using data hiding and polymorphism at every corner.

Re:Pretending OOP Never Happened by dingoegret on Saturday May 16, 2020 @11:23AM (#23204123)
Okay so it just sounds to me like careful OOP. Discipline just isn't a paradigm. It's an ethic and it's a good ethic. I love OOP, I've always built systems I was proud of with it. But to do that effectively you do need discipline and you need experience in design. Which most people think they can just skirt blindly by writing procedural code in class wrappers and the system will just magically emerge. This results in even more horrific programs when combined with blind TDD. This has always been nonsense. I don't use much OOP nowadays due to my work prioritizing efficiency and speed. Which OOP fails at miserably. But every now and again I'll find myself doing some monomorphism that looks like vtable dispatch without ever touching heap. And I'll kindly look back on my OOP days.
Re:Pretending OOP Never Happened by pacala on Saturday May 16, 2020 @01:26PM (#23205361)
Agreed. Careful/Restrained OOP / 95% FP [the ML branch] / DOP, all names for the same sweet spot. The push back is against hard OOP culture, rooted in confused thinking.

* Everything is emphatically NOT an object. There is immutable data, there are functions and there are modules. These are not the interchangeable, learn to use them in the proper context.

* Everything is emphatically NOT a message. There are plain functions, there are polymorphic functions, there are RPCs and there are async messages. These are not interchangeable, learn to use them in the proper context.

Re:Pretending OOP Never Happened by kristopolous on Friday May 15, 2020 @12:29PM (#23194059)
The formalism and structure IS the critique. If the tools become about the rules they hinder instead of help.
Re:Pretending OOP Never Happened by _0ffh on Friday May 15, 2020 @07:00PM (#23198515)
Yes, two points:

1. To try to force restrictions on someone who doesn't understand the reasons for these restrictions will not magically ensure good results.

2. To try to force restrictions on someone who does understand the reason for these restrictions is just a pointless nuisance.

Re:Pretending OOP Never Happened by _bxg1 on Friday May 15, 2020 @07:13PM (#23198601)
Personally I've lumped procedural and OOP together into a single concept. Put differently, I consider OOP as a strict successor to procedural. Both are about side-effects and state, OOP just gives you smarter tools for encapsulating and managing that state. FP lets you eliminate some state altogether. My personal philosophy is to eliminate as much state as you can, and then to smartly manage what's left.
Re:Pretending OOP Never Happened by jbverschoor on Friday May 15, 2020 @08:27PM (#23199162)
State is simply scoped more often.

It’s all just a bunch of functions and memory

Re:Pretending OOP Never Happened by lkrubner on Friday May 15, 2020 @05:35PM (#23197748)
Hmmm, I have to push back against that somewhat. Do you have specific examples? I am mostly known for arguing against OOP and for Functional, and when I was doing my research most of the essays I read were aware of the Functional paradigm. Please see here:

http://www.smashcompany.com/technology/object-oriented-progr...

Re:Pretending OOP Never Happened by etangent on Friday May 15, 2020 @12:20PM (#23193924)
There is a (mistaken?) tendency to see OOP as a step beyond functional programming, probably because it allows for more complicated abstractions.

But it seems to me a more natural scale to use is how the state is managed. In procedural programming state is global, which is messy. OOP cleans it up a bit by splitting global state into pieces and making them local. Functional programming takes this even further by avoiding having to reason about state altogether.

Re:Pretending OOP Never Happened by Jtsummers on Friday May 15, 2020 @12:29PM (#23194045)
Procedural code doesn't require global state, though it doesn't discourage it either. The mid-point between procedural global and OO encapsulated state is the use of structures/records to contain state, but without any associated (that is, no language enforced association) methods/functions. You instantiate a new state record, and pass that around.

This is a natural way to work on procedural code (in an iterative development fashion) to move from prototype/early code to stable, maintainable code. You create global variables, then encapsulate them in a record (perhaps keeping just one record globally), then you move to passing a record around (permitting multiple states to exist side-by-side). Ideally you'd start with passing the record around, but sometimes you just write code to get the concept implemented and refactor from there.

Re:Pretending OOP Never Happened by zozbot234 on Friday May 15, 2020 @12:39PM (#23194168)
Language-enforced association with methods and functions makes it possible to have full encapsulation in a way that's guaranteed by the language itself. That's not even OO either, just simple object-based code. You even see these same ideas entering FP nowadays, with things like univalent types that enable one to establish equivalent types as "equal", and thus fully interchangeable, no matter how they're structured internally.
Re:Pretending OOP Never Happened by Jtsummers on Friday May 15, 2020 @01:03PM (#23194494)
Yes, this is true. I'm not arguing that procedural (in the C-style) is fantastic, but it can be scaled decently well, but requires discipline. There are definitely improvements to be had when you start adding other language features that enforce certain things.
Re:Pretending OOP Never Happened by zozbot234 on Friday May 15, 2020 @01:15PM (#23194654)
The whole problem with "discipline" is that it's hard to write it down and enforce it in a large-scale project. Language-based mechanisms are inherently scalable (at least if properly designed; and arguably e.g. implementation inheritance isn't) and make it much easier to build software "in the large".
Re:Pretending OOP Never Happened by walshemj on Friday May 15, 2020 @06:09PM (#23198101)
How large a team I have certainly worked on large scale projects using procedural languages and I don't recall any problems with the required discipline
Re:Pretending OOP Never Happened by walshemj on Friday May 15, 2020 @06:07PM (#23198077)
And having done a fair bit of procedural programming back in the day F77 and PL1/G only the most trivial of programs have every thing as global state.
Re:Pretending OOP Never Happened by devericx on Friday May 15, 2020 @05:58PM (#23197996)
How does functional programming allow you to avoid reasoning about state? Don't you always have to reason about state, no matter what paradigm you use? Otherwise, how are you going to build anything useful if you're not keeping track of any variables?
Re:Pretending OOP Never Happened by AnimalMuppet on Friday May 15, 2020 @08:30PM (#23199186)
The following words are bad: shared, mutable, global.

All programs have state. It's worse if the state is shared. It's worse if the state is mutable. It's worse if the state is global. It's the worst if it's all three.

Functional program still have state. They typically don't have shared state, or mutable state, or (much) global state, though. Those are real wins.

Re:Pretending OOP Never Happened by Gibbon1 on Friday May 15, 2020 @08:51PM (#23199362)
I feel like a lot of functional programming tries to hide state behind an OS/API call of some sort. Sometimes that makes sense. Think a program that transforms data into SQL calls.

Mirror of that is a driver for a piece of hardware. You got multiple levels of state going on.

Re:Pretending OOP Never Happened by agumonkey on Friday May 15, 2020 @04:19PM (#23196941)
slight historical anecdote, back when graphical user interface were not a thing, sutherland team is said to have developped rendering in a fully streamed style. Relying on state too much is also a matter of context, that team didn't have memory really (at least nothing that could store a frame buffer) so they wrote imperative code in an iterable fashion.
Re:Pretending OOP Never Happened by user234683 on Friday May 15, 2020 @01:01PM (#23194474)
As you demonstrate, there's not always a clear-cut distinction between OOP programs and non-OOP programs. Sure, the Linux kernel uses some OOP concepts, but it couldn't be described as fully OOP in aggregate. In my opinion, the best paradigm is no paradigm at all: use the right tool for the right job.
Re:Pretending OOP Never Happened by munchbunny on Friday May 15, 2020 @01:10PM (#23194595)
"Use the right tool for the right job" is the final stage of "first, learn the rules, then learn when to break them."

The difficulty with that advice is that it doesn't become applicable until you've gotten maybe 5 years worth of humble pies thrown in your face, for moments where you realize in retrospect that you made the wrong call.

Until then you don't have enough of a body of experience to tell two very different scenarios apart: "things are this way because my predecessors already learned the hard lessons" vs. "things are this way because my predecessors didn't know better."

Paradigms are really useful in practice as a shortcut to implementing 80% of the wisdom until you begin to learn the remaining 20%, and one of the roles of senior engineers is to make sure junior engineers don't mistake the 80% for the 20%.

Re:Pretending OOP Never Happened by a1369209993 on Friday May 15, 2020 @09:57PM (#23199781)
> "things are this way because my predecessors already learned the hard lessons" vs. "things are this way because my predecessors didn't know better."

This is just Chesterton's Fence again, and has the same refutation: if you're putting up a fence that serves some long-term purpose, it is your responsibility to put signs on it explaining what that purpose is. Otherwise people are right to assume it's one of the overwhelming majority of fences that do not, in fact, serve any purpose beyond obstructing their movement.

Re:Pretending OOP Never Happened by munchbunny on Sunday May 17, 2020 @06:41AM (#23211069)
if you're putting up a fence that serves some long-term purpose, it is your responsibility to put signs on it explaining what that purpose is. Otherwise people are right to assume it's one of the overwhelming majority of fences that do not, in fact, serve any purpose beyond obstructing their movement.

Your approach absolves yourself of exercising agency to discern purpose in fences that lack signage in a real world where plenty of purposeful fences lack signage. Just because it’s someone else’s responsibility doesn’t mean they actually do it, and just because they didn’t put a sign there doesn’t mean you’re blameless if you cause a service outage.

I work with cryptography and it would take a book to put adequate signage on some fences. Nobody’s going to write why they’re using AES GCM every time they use AES in that code.

Re:Pretending OOP Never Happened by mdpopescu on Sunday May 17, 2020 @01:37AM (#23209830)
Sure, that makes a lot of sense, and it's a sensible demand when there are a million people, each putting up one fence.

However, when you write the same architecture over and over, you don't write an explanation each time - for various reasons, a not insignificant one being that there's no obvious place to put it. (I use dependency injection in my code. Where should I explain why - on each class? In a separate file in the project? In the documentation? Should I write documentation, then? What if I write it and others continue to "new" services inside their code?)

Re:Pretending OOP Never Happened by slifin on Friday May 15, 2020 @07:20PM (#23198652)
There's a good talk here from Uncle Bob who helped popularise the solid principles, about what OOP is, and isn't

https://youtu.be/QHnLmvDxGTY

Towards the end of the talk note of the stickers on his laptop

Re:Pretending OOP Never Happened by michaelbrave on Friday May 15, 2020 @02:38PM (#23195697)
I don't know much bout procedural programming, but I'm curious about your thoughts on swift protocols and also how swift allows classes to be used in a functional way. I wonder if this could mitigate some of those problems?
Re:Pretending OOP Never Happened by badrabbit on Friday May 15, 2020 @12:41PM (#23194199)
Design and framework are different things though. You can use oop design when needed without having the whole world evolve around it.
Re:Pretending OOP Never Happened by ferzul on Friday May 15, 2020 @09:04PM (#23199434)
similarly, there's elm/redux style state management, which is almost explicitly about emulating global state in a functional architecture.

it seems all programming paradigms end up devolving into global state by friday. because too many developers cannot recognise global state, instead recognising only its syntax

Re:Pretending OOP Never Happened by it on Friday May 15, 2020 @05:49PM (#23197900)
Hidden information is entropy. Do you really want more of that in your system?

https://physics.stackexchange.com/questions/29175/why-is-inf...