1. 46
  1. 20

    Seems like it’s time to post the venerable closures-vs-objects koan again. 🕉

    1. 7

      I remember realising that halfway through SICP and 🤯, so good. I put a link to my take on it at the end of the post.

    2. 17

      I don’t disagree with the point the article is making, but they specifically picked an example where OOP would be a weak solution. The objects have no state to keep track of except for the root URL, and quite frankly, that would probably never change during runtime in such an API.

      What if the object had to keep track of and manage an API key for that endpoint? What if the object has to round-robin different endpoints or just swap to another endpoint if the first one doesn’t respond? What if… And so on.

      The proposed refactor is perfectly reasonable in the case presented, but it might also only apply to that case and with those limitations. And honestly, there is nothing special about OOP or FP or any other paradigm. You could program anything in any paradigm, but some of them are easier to reason about when the problem gets large enough. There’s no need to go spewing patterns everywhere, but “this case doesn’t need OOP” is not a good enough case to remove all OOP around the codebase.

      1. 9

        Use the right tool for the right job.

        1. 30

          Really not a fan of this quote. How do you know what the right tool is? How do you know what the right job is? “Use the right tool for the right job” is like saying “write good code, not bad code.” It doesn’t inform us.

          1. 17

            The phase is best used among the already informed when making a decision about what is available. To the uninformed I would simply say “do not be dogmatic.”

            1. 6

              I agree that this quote can be frustrating and has the property that its wisdom is most visible in hindsight.

              But the way I interpret this is not to mean “You should know the right tool”, it’s to draw attention to the fact that the right tool is context sensitive. “The right job” part of the quote is critical. It’s saying that a blanket answer doesn’t work because the solution depends on at least one parameter. Seek out those parameters and then the solution becomes more obvious.

              1. 4

                How do you know what the right tool is? How do you know what the right job is?

                You know what the right tool is by getting good at lots of different tools, thinking about them a lot, reading what people have written, and talking with people about what they have experienced. You know what the right job is basically via the same method.

                As c12 has pointed out, when you rely too much on other people’s experiences, or those experiences are not varied enough, you can fall into the trap of dogma. If you rely too much on your own experiences, without considering the wisdom of others, you can fall into the trap of egotism.

                I like this quote a lot because it’s what broke me of my antipattern of looking for the Perfect Programming Language. The smartest person I knew was doing a game jam and writing the game in C, and I asked why they would use something so backwards instead of Haskell or OCaml or Lisp or anything else. Their response was that C was a good tool for what they were trying to do.

                1. 2

                  Could not agree more. In addition to your questions: Should you choose the perfect-fit tool that you’ve never used before or the fits-ok tool that you’re an expert in? What about the perfect tool that you’re an expert in but no one else on the team has used before? And if we’re just talking about language paradigms in a code base, how do you prioritize problem-paradigm fit versus consistency?

              2. 7

                My go-to for python anymore is

                • immutable dataclasses
                • bag-of-functions, in rare cases functions on the classes themselves

                And, most importantly: If I find myself using the import statement, that’s the signal that it’s time to swap to Go. I’m done wrestling with pythons “abandoned burning landfill” approach to packaging and dependencies, there are better things to spend life on.

                1. 5

                  I feel like the author’s points are very muddled because of the need to attack the term “object-oriented” as designating some particular style of code or language design. Python is object-based, which means that even when using data classes and functions, one is still using objects.

                  To make this clearer, let’s translate the example code into a flavor of E. E does not have classes but every value is an object.

                  def authorAPIMaker(constructURL):
                      return def makeAPIClient(rootURL :Str, sessionMaker :SessionMaker):
                          return object self:
                              to getItems(entity :Str):
                                  def resp := requests<-get(`$rootURL/${constructURL(entity)}`)
                                  return when (resp) ->
                                      [for n in (resp.json()["items"]) makeItem(n)]
                              to saveItems(entity :Str):
                                  sessionMaker<-withScopedSession(fn session {
                  def constructDefaultURL(entity :Str):
                      return `/v1/$entity`
                  def constructClientAURL(entity :Str):
                      return `/$entity`
                  def constructClientBURL(entity :Str):
                      return `/a/special/place/$entity`
                  def clientA := authorAPIMaker(constructClientAURL)("https://client-a", sessionClass)

                  Classes (“objects”) are replaced with closures, but all values are objects, including the functions. Inheritance is replaced with composition. The detailed work that the example does is more apparent; we can’t do I/O synchronously, and so we are forced to factor the code so that URL construction doesn’t conflate ownership of the root URL value (which belongs to only the API client) with ownership of the path (which belongs to the URL constructor).

                  I think that the biggest difference between the author’s style and the E/Scheme/objects-as-closures style of design is that data classes are antipatterns in E. Rather, we are deliberately bundling state and behavior together, and if only a few behaviors ever need some state, then we should isolate that state within an object and put the behaviors into its methods.

                  1. 2

                    Thanks for a really clear example.

                    Refactoring into closures like this makes it more apparent that my distaste isn’t so much for the object-y sugar-y stuff, but the act of bundling data + functions as a default approach, whether that be in object form or in closure form. There’s a place for it, but due to the simple fact that it’s harder to serialise/print a closure/object - if you can avoid constructing these things, you may as well not bother. (I’d be interested to know how E deals with this).

                    I have some big regrets about not spending more time on an example, which I kinda just threw together - I think a lot of the criticism of conflation of concerns in the class are correct. However, I still back myself to refactor most of what gets thrown at me into something as sane/saner :-)

                  2. 5

                    Sure, python does make it easy to write functions, and you can code in a “big bag of functions” kinda way, but why would you do that in a language that is specifically designed to work in an OO way? It’s like looking at Haskell and saying, “You know what this needs? Objects”

                    1. 11

                      Did you mean: Objective Caml?

                      1. 6

                        A massively underappreciated language.

                        1. 1

                          Oh dear god, does such a thing actually exist?

                          I’m sorry…

                          1. 4

                            I can’t tell if you are joking or not so I’m just going to post the ocaml wiki and slowly back away.

                            1. 2

                              I was kinda joking, but I did look up the wikipedia entry for OCaml and I was impressed, and a little horrified :)

                            2. 3

                              one of my favourite languages! you’re missing out if you don’t at least give it a look.

                              1. 2

                                Why you’re sorry? Rust’s compiler initially was written in OCaml. MLdonkey, not really used that much anymore, is written in OCaml, and it’s a pretty big codebase. Static analyzers for C/C++ (Frama-C, Infer) are written in it. Reference interpreter of WebAssembly, compiler for Haxe language, Facebook Messenger.

                                Did you know that Pascal has its own Object Pascal version too? It was called Delphi and it was hugely popular in the 2000s.

                                1. 4

                                  Pascal went a lot further than that.

                                  Pascal evolved to greater modularity with Modula, which was quickly replaced with Modula-2. This was intended to suitable for OS development, but this proved premature. For instance, in parallel with the invention of the ARM processor, Acorn attempted to develop a new OS for it in Modular-2. The effort failed and was replaced with Arthur, later renamed RISC OS.

                                  It was one of the fastest compilers for MS-DOS for some years, though, especially Topspeed Modula-2.

                                  Pascal creator Prof Niklaus Wirth went back to the roots, stripped the language back hard, and created Oberon. Oberon is a modular, stripped-down Pascal successor. It is both a language, an IDE, and a minimal OS to support that IDE. The entire OS including IDE, editor, compiler, and UI is about 30,000 lines of code.

                                  Oberon developed in several directions. The successor language, Oberon-2, was used to write a more complete OS, called Linz Oberon: https://ssw.jku.at/Research/Projects/Oberon.html

                                  Prof Wirth was still dissatisfied and went back to Oberon and created Oberon-07 (in 2007) and re-implemented the OS as Project Oberon: http://www.projectoberon.com/

                                  Meanwhile, another team wanted threads and multiprocessor support. They added OOPS and created Active Oberon, and created a graphical version of the OS with a zooming UI called Bluebottle. The OS itself is now called A2. https://en.wikipedia.org/wiki/Bluebottle_OS

                                  1. 1

                                    Object Pascal lives on in FreePascal and Lazarus! Not super popular but still going.

                              2. 2

                                but why would you do that in a language that is specifically designed to work in an OO way?

                                Where did you get this idea from? It most certainly was not. The documentation clearly says that it is a multiparadigm language and that OOP is an optional feature.

                                The OP answers you question anyway. The answer is because it is pointless. There is no advantage to adding half a dozen of syntactic elements and extra boilerplate code.

                              3. 4

                                If you start with an example of flawed OOP design and them make any conclusions about OOP, they have no value. Every paradigm or technology can be used in a wrong way – such information is pointless.

                                1. 5

                                  People seem to miss the point of OO. The point is about names. The example defines save_items, but realistically, to avoid name clashes, it would be client_save_items. Similarly, “bag of functions” leads to names like treemap_add and hashmap_add, which is redundant, because it can be inferred from the runtime type of the first argument. The point of OO is to avoid redundancies of TreeMap and treemap_add and HashMap and hashmap_add and collapse them to add.

                                  1. 6

                                    For purely namespacing purposes I personally prefer modules over classes in Python, in a lot of cases. Put a collection of functions into foo.py, then import foo from another file, and all those functions are nicely namespaced as foo.name. Admittedly classes vs modules depends a lot on the project and programming style.

                                    1. 2

                                      treemap.add is not really an improvement over treemap_add. The point is you write m.add(k, v), not m.treemap_add(k, v). Compare treemap_add(m, k, v) and treemap.add(m, k, v).

                                    2. 3

                                      I think I can safely speak for Alan Kay when I say that this is not the point of OO.

                                      The points of OO are dynamic/late binding, encapsulation, and inheritance, IMHO and IIRC.

                                      However, I agree that OO-style naming is a good thing. You can have it without objects using function overloading, which pairs well with a universal-function-call syntax as found in Nim and some other languages.

                                      1. 5

                                        “Inheritance” was definitely not included in Kay’s famous quote:

                                        OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

                                        I argue that inheritance is an anti-pattern, and is in large part responsible for OO hate. OO with composition only and immutable value objects can be pretty nice.

                                        EDIT: This got me to search for something more specific on Kay’s feelings about inhertiance, and I found this great post by him on Quora

                                        1. 3

                                          These days Alan Kay says that OO is about message passing. I’ve seen some debate about whether that was always his message. hwayne probably has a more informed opinion on this than I do.

                                          1. 5

                                            Given the history, I suspect that Alan Kay did a very poor job of explaining what he did and has been yelling at us ever since for failing to read his mind correctly.

                                            1. 1

                                              Dealers of Lightning has a chapter on how Kay was disappointed with Smalltalk and wanted to start again, but the team basically told him no.

                                              BTW: When I asked him about Self, he told me:

                                              I liked Self. “Good OOP” is still waiting for a much better notion to replace the idea of a “Class”

                                          2. 1

                                            BTW: Only when I’ve read the book Moldable tools, I’ve finally got some of Kay’s points of OOP and “modeling”.

                                            1. 1

                                              If OO is about dynamic/late binding etc, C++ without virtual functions shouldn’t be OO at all. But to the contrary, C++ without virtual functions looks and feels like C with OO, and virtual functions feel like a superfluous addition.

                                              1. 3

                                                C++ without virtual functions shouldn’t be OO at all.

                                                Alan Kay would agree: “I made up the term object-oriented, and I can tell you I did not have C++ in mind.”

                                            2. 1

                                              The point was about “enterprise environment”, design patterns and Java eating our lunch. Ohh, how many times have I seen people complaining about perl using an arrow instead of a dot.

                                              1. 1

                                                An arrow? That’s so silly — ooRexx uses a ~ twiddle.

                                            3. 3

                                              @pushcx, this is a follow up to https://lobste.rs/s/ldzfsw/oo_python_is_mostly_pointless, please fold it into that submission.

                                              1. 3

                                                Now drop type declarations and see how it gets even more readable.

                                                1. 3

                                                  I think this debate needs a bit more data, so I’m considering making a “call to arms for Python OO enthusiasts” at some point, my plan is:

                                                  • make a github repo, people raise PRs with their code (do I require tests?)
                                                  • their code should be the most idiomatic OO, least refactorable into data + functions
                                                  • no operator overloading specific stuff
                                                  • max 500 lines + I’m only going to look at 5 submissions - I’ve not got all the time in the world
                                                  • I attempt to expunge the OO and present my results - the mob decides

                                                  Is this fair or useful or just dumb?

                                                  1. 3

                                                    I’d play. I mostly agree with your article but think there’s a better case to be made for OO with immutable value objects. Meaning: I think I could write object solutions that would be more or less equivalent to any bag-of-functions approach, both in readability and verbosity.

                                                    1. 1

                                                      I don’t think I could fit a readable submission into 500 lines, but a common (and classical) use for OO is polymorphic evaluation of simple abstract syntax trees for small expression/rule languages. Each different meaningful term or syntax element is a class, and every class has an .Eval() method (usually they take a context object as the argument). To evaluate an expression or a rule, you .Eval() the top level object for the entire rule’s AST, and it .Eval()s its children as appropriate, and so on. Nothing has to know anything about what all of the different terms, operators, elements, and so on are, which makes it quite easy to add more, tinker with the internal implementations, and so on.

                                                      The nicest Python non-OO version I can think of would be to have the AST nodes be bag-of-data objects (different data for different types of nodes) with a common ‘type’ field that named their type. Then you would have a big dict mapping types to their evaluation functions; these functions would take the node bag-of-data object and a context object as arguments. You could also implement a giant ‘eval’ function that knew all of the node types and how to evaluate each one, but that gets ugly.

                                                    2. 2

                                                      In general I agree with this article’s thesis. See also my co-worker Jack Dietrich’s excellent Pycon talk Stop Writing Classes.

                                                      However as others have states, the examples you give don’t make the most compelling argument.

                                                      You’re saying “passing parameters around is no big deal. Who cares?”

                                                      The actual answer unfortunately is “You will if your code base’s size and complexity gets large enough.”

                                                      There are solid reasons for choosing objects as an encapsulation abstraction when the problems being solved are very complex.

                                                      For instance, I’m trying to envision what writing a control plane for a very complex distributed system would look like using nothing but methods and parameter passing, and it’s not pretty.

                                                      Even languages which have wisely eschewed objects like Clojure and Go have similar constructs which avoid the bad bits (inheritance) while encouraging the good ones (polymorphism).

                                                      So I’d suggest a slight change in tack - why not either explicitly call out the fact that what you’re asserting mostly applies to small to moderately complex programs, and that for larger more complex programs some other equivalent abstraction is called for?

                                                      1. 1

                                                        Historically, I did a lot of Java, then D, and then I came back to python. I was tired of java-like OOP style and (inspired by lisp and functional programming) used a lot of functions in my python scripts and programs.

                                                        Last few years, I am steadily returning more and more to the OOP style, mostly because when it is done right, it can lower the cognitive load. It can be a great tool to make your programs more comprehensive (Clean code ftw), and set certain logical order to things, so that the orientation and understanding of the project can be easy. But when overused, it can be a nightmare.

                                                        1. 1

                                                          Interested in what people come up with from this challenge. I really like Python classes for a lot of code organizational purposes, but the best uses I’ve seen are things that don’t really look like what you would see in, say, Java.

                                                          I have a couple “good uses of Python classes” (hint: they’re cases where you often want multi-line functions + closures, and bundling, so dicts of lambas really don’t cut it), but would those be “idiomatic OO”? Probably not.

                                                          1. 1

                                                            Well duh, if Python OO wasn’t pointless it would use -> syntax, not a dot!