[HN Gopher] The world's loudest Lisp program to the rescue
       ___________________________________________________________________
        
       The world's loudest Lisp program to the rescue
        
       Author : kryptiskt
       Score  : 86 points
       Date   : 2024-05-02 07:50 UTC (15 hours ago)
        
 (HTM) web link (blog.funcall.org)
 (TXT) w3m dump (blog.funcall.org)
        
       | nemoniac wrote:
       | Is it really established wisdom that multiple inheritance might
       | be an anti-pattern? Anyone care to elaborate?
        
         | nvy wrote:
         | Isn't it the ambiguity of the Diamond Problem? Suppose B and C
         | are both children of A, and D is a child of both B and C.
         | 
         | If B and C both have methods foo(), which gets called when you
         | do d.foo()?
         | 
         | Seems like a real footgun requiring extra effort to avoid.
        
           | phoe-krk wrote:
           | In CL's solution, the order of superclasses matter to avoid
           | ambiguity. If D is defined like (defclass d (b c) ...) then a
           | method specialized on B is called; if like (defclass d (c b)
           | ...) then it's otherwise.
        
             | pfdietz wrote:
             | And sometimes more than one method is invoked, using a
             | sophisticated method combination infrastructure.
        
               | phoe-krk wrote:
               | Right, I assumed the default method combination, and also
               | the simplest case of it with no around/before/after
               | methods being defined... Golly, CL object system is
               | complicated, now that I look at it from this perspective.
        
               | tmtvl wrote:
               | It's simple when you want it, and powerful when you need
               | it.
        
             | jerf wrote:
             | In this case the problem becomes that while one can define
             | a 100% consistent, coherent order for the compiler to use,
             | the _human 's_ ability to understand what will happen when
             | they call a method of a particular name, and also what that
             | resolution method will do as the code is refactored and
             | changed over time, exceeds anything a human can be
             | reasonably expected to have.
             | 
             | Really, all the problems with multiple inheritance are that
             | the humans can't handle the complexity that results. The
             | compilers can be made to do "something" that is arguably
             | sensible.
        
         | mark_undoio wrote:
         | I think the implementation in C++ put it out of fashion, as
         | later languages (e.g. Java) deliberately restricted it to avoid
         | the complexity. The main criticism I saw was the potential for
         | (variants of) the "diamond" where A is subclassed by B and C,
         | then both of those are subclassed by D. Does D get two copies
         | of A's state? It's hard to come up with an intuitive behaviour.
         | 
         | More recently, the move seems to be away from class based
         | object orientation (including inheritance) entirely.
         | 
         | On the other side of things, I've never heard people talk about
         | Python's multiple inheritance with the same tone used for C++ -
         | but then there are cultural differences in the language
         | communities too.
        
           | fiddlerwoaroof wrote:
           | Something I've found interesting is that most widely-used
           | class-based inheritance languages eventually added multiple
           | inheritance of implementations back in: PHP added traits that
           | can contain method implementations; Java added default
           | implementations on interfaces; etc.
        
             | lmm wrote:
             | The famous "super considered harmful" post (
             | https://fuhm.net/super-harmful/ ) pointed out the key
             | problem with diamonds, and it's mainly a problem with
             | constructors. Allowing mixins that can have method
             | implementations but only allowing one class parent with a
             | constructor is a pretty good spot in the design space, and
             | is what a lot of languages have converged on.
        
               | fiddlerwoaroof wrote:
               | I like CL's solution to constructors which is basically
               | "specialize this generic function (SHARED-INITIALIZE or
               | INITIALIZE-INSTANCE) with an :AFTER method". You reliably
               | run all the initialization code for each class involved
               | and you don't have to remember to call CALL-NEXT-METHOD
               | (CL's spelling of super)
               | 
               | Edit: I see that post refers to Dylan, which is more like
               | CL than python in the important ways. IMO, sleeping on
               | CL's object system CLOS was a huge mistake of the
               | "Java/C++ era" of our industry.
        
           | bitwize wrote:
           | > Does D get two copies of A's state? It's hard to come up
           | with an intuitive behaviour.
           | 
           | C++ gonna C++, which means the language covers _all_ the
           | bases because some programmer might get mad if their use case
           | wasn 't accounted for.
           | 
           | C++ has something called virtual inheritance, wherein if
           | subclasses B and C inherit _virtually_ from A, any subclasses
           | of both B and C will get one copy of A 's state. Otherwise,
           | they will get two copies: one from B and one from C.
           | 
           | This solves the problem of addressing concerns of all
           | programmers w.r.t. the diamond inheritance problem, but makes
           | the language more complex (and triggers my CPPPTSD).
           | 
           | https://en.wikipedia.org/wiki/Virtual_inheritance
        
         | pfdietz wrote:
         | A nice pattern from Common Lisp is to inherit the parts of an
         | object from different superclasses. Method combination means
         | one can write methods for those superclasses and then have them
         | automatically combined in a subclass.
         | 
         | Example: if one has tree nodes with various slots that
         | represent children and you want to write a tree traversal
         | function, you put each slot in a superclass, inherit from those
         | superclasses in the correct order, and then write a method for
         | each superclass that calls the child at that slot. The methods
         | are combined in the right order automatically in a PROGN method
         | combination.
        
         | jolt42 wrote:
         | Meh. Probably a reaction to getting "burned" by it. But show me
         | something you can't get burned by.
        
         | copx wrote:
         | 90s-style Java OOP showed everyone that heavy use of multiple
         | inheritance is the worst thing since 80s-style BASIC where ever
         | third line was a GOTO.
         | 
         | Imagine one class inheriting from 50 other classes through
         | multiple inheritance..
         | 
         | People really used to construct classes like:
         | 
         | "Iron Sword inherits from Iron which inherits from Metal which
         | inherits from Meltable (which inherits from Temperature) and
         | Material. But of course it also inherits from Sword which
         | inherits from Weapon and Edged. Meanwhile Weapon inherits from
         | Equipment which inherits from Ownable and Item which.." and so
         | on.
         | 
         | Basically you make every aspect and attribute of an entity a
         | class and then create your entity's class by mushing together
         | all those classes through multiple inheritance. The results
         | are..not pretty.
         | 
         | Such code quickly becomes very hard to comprehend and maintain.
        
           | mikepurvis wrote:
           | Yup. No amount of generated documentation or static analysis
           | can make up for the cognitive load required to reason about
           | where a particular method is actually being dispatched to
           | under those conditions.
        
           | Jtsummers wrote:
           | 90s Java did not have multiple inheritance (nor does today's
           | Java). It did have multiple interfaces, but they only carried
           | a spec of the interface and no implementation details beyond
           | that. C++ was the one with multiple inheritance, if you are
           | trying to reference a popular 90s OO language.
        
           | anthk wrote:
           | OOP would work fine for a text adventure, such as Inform6
           | against the Z-Machine, which pretty much the gameplay
           | rooms->objects it's perfect for this. For everything else...
           | well... maybe just CLOS it's usable enough.
        
           | bitwize wrote:
           | 90s Java didn't do that because Java doesn't support multiple
           | class inheritance.
           | 
           | 90s C++, however, did.
           | 
           | Funny you should cite a game example. I once read about how
           | the developers of StarCraft[0] ran into the same Goddamn
           | inheritance problems I did when trying to build a custom game
           | engine and a game with that engine. Adding behaviors via
           | inheritance _seemed_ like a good idea _at the time_ (mid-late
           | 90s), especially given all the propaganda we read from our
           | C++ compiler manuals and such. But it turned into a situation
           | where you either accepted multiple inheritance with all of
           | its complexity and suck, including  "which of the multiple
           | base classes that implement 'foo' do I want when I call
           | derived::foo()?" -- or resorting to delegates or other
           | methods of composing behavior.
           | 
           | Me, for gaming, I became an ECS convert and haven't looked
           | back. There are some pain points when writing a game in ECS
           | style... but the advantages pay for the relatively minor pain
           | many times over.
           | 
           | [0] https://www.codeofhonor.com/blog/tough-times-on-the-road-
           | to-...
           | 
           | CFlingy is a particle spawner. Why does that have to be in
           | the inheritance chain, instead of a trait you _add_ to an
           | object?
        
       | mark_l_watson wrote:
       | Great writeup! I am a long time user and fan of Common Lisp, and
       | this is one of the more interesting use cases I have seen!
        
         | varjag wrote:
         | Thank you Mark! There are blessed and cursed projects out
         | there, and this one has definitely been the former.
        
       | varjag wrote:
       | Author here, if you have any questions.
        
       | anthk wrote:
       | On Common Lisp, I loaded a nearly 30 yo eliza Chatbot written in
       | CL, it ran almost straight under SBCL with just omitting an
       | error:
       | 
       | https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/areas...
       | sbcl --load eliza.lsp            (top-level)            (hello
       | how are you)
       | 
       | Do not use punctuation. Use (goodbye) to exit.
       | 
       | From a Unix user like me, SBCL/CL looks a bit bloaty and non-
       | Unix, but I have to acknowledge that CL and Emacs' Elisp had a
       | great history on compatibility and easyness due to the
       | homoiconicity. In plain English: everything it's handled in the
       | same way everywhere. The syntax will be the same on every
       | function.
        
       ___________________________________________________________________
       (page generated 2024-05-02 23:00 UTC)