[HN Gopher] Design Patterns Are Temporary, Language Features Are...
       ___________________________________________________________________
        
       Design Patterns Are Temporary, Language Features Are Forever
        
       Author : delifue
       Score  : 75 points
       Date   : 2024-09-09 10:05 UTC (12 hours ago)
        
 (HTM) web link (ptrtojoel.dev)
 (TXT) w3m dump (ptrtojoel.dev)
        
       | 082349872349872 wrote:
       | I haven't looked at patterns since the GOF days, so maybe they
       | grew closer to Alexander's as they matured, but the original ones
       | were very much ways to speak about things so they'd be
       | politically palatable in an everything-is-an-object world.
       | 
       | You just wanted some data? "Flyweight". You just wanted some
       | functions? "Strategy". etc.
        
         | HelloNurse wrote:
         | An insightful comment, but to be fair many important GoF design
         | patterns like Composite and Decorator are genuinely object
         | oriented, leveraging polymorphism and interfaces to produce
         | order and elegance.
        
           | vkazanov wrote:
           | Order and elegance?
           | 
           | Some people say that GoF patterns are just a way to cover up
           | for OOP-centric language shortcomings.
        
             | lunchmeat317 wrote:
             | They absolutely are, and aren't strictly necessary in many
             | functional or multiparadigm languages.
        
               | jerf wrote:
               | FP languages have their own patterns. Some of them can be
               | plausibly cast as "fixing weaknesses" in them, too.
               | 
               | Multiparadigm languages end up with lots of patterns you
               | need to learn, because while multiparadigm languages may
               | support many approaches to a given problem, generally
               | there are definitely better and worse approaches,
               | specific to the language in question, and between the
               | number of options, the subtly of their implementation
               | details and how those interact with the problem, and the
               | specific preferences of the language itself (because all
               | multiparadigm languages still have _preferences_ ), it
               | can take a multiparadigm language community a decade to
               | work out the best pattern for a given task. The existence
               | of many options inevitably expands the number of _wrong_
               | options, or if you prefer,  "distinctly suboptimal"
               | options, as well.
        
             | HelloNurse wrote:
             | Object-oriented order and elegance compared to an object-
             | infested mess of special cases and complications. Remember
             | that 25 years ago C++ wasn't a pleasant place and _Design
             | Patterns_ was an impressive step forward.
        
               | teqsun wrote:
               | > "Remember that 25 years ago C++ wasn't a pleasant
               | place"
               | 
               | 25 years later it still isn't (joking... kind of)
        
               | vkazanov wrote:
               | Oh, I do remember the grim story really well. I was
               | there. I still have all the books. And the scars.
               | 
               | Back then I was a young innocent programming soul trying
               | to reconcile C++ and GoF and UML and all that. And Modern
               | C++ Design. And const here const there const copy
               | constructor by reference.
               | 
               | Anyway. I like vanilla C. And Go at times.
        
               | kevstev wrote:
               | I spent about 12 years doing C++ at the very peak of its
               | OOP hype and did the same... then finally left, certain
               | that I would hate doing "real" work in any other
               | language, and a funny thing happened...
               | 
               | I realized I was suffering from severe Stockholm
               | Syndrome. Node.js was just.... so gd easy to do anything
               | in. Maybe too easy (a la leftpad), but build/deploy
               | cycles were instantaneous, adding new libraries was easy-
               | again maybe too easy, JS has a tiny fraction of the
               | footguns and you don't have to know them all to write
               | code that isn't going to blow up on you or cause memory
               | leaks and performance issues.
               | 
               | Python... was the same. I have even taken some dives into
               | Java, and while I don't love the architectural monuments
               | Java devs tend to produce, its still less of a hassle
               | than C++.
               | 
               | I was so happy when there was a move towards templates
               | and data oriented design and the priests of OOP were
               | getting knocked off their pedestals- for a period you
               | were at risk of getting shunned for not kneeling at the
               | gods of OOP and the GOF book and UML diagrams.
        
         | readthenotes1 wrote:
         | It amused me that Alexander's _A Pattern Language_ was 90%
         | about habitability and 10% about building whereas I had the
         | feeling GoF was the opposite.
         | 
         | That is, I believe Alexander's version of design patterns would
         | have things like "Intention Revealing Names" and code reviews
         | focusing on "Clarity, Consistency, and Correctness (in that
         | order)"--things that help subsequent developors live in and
         | enjoy the code and design.
        
           | PaulDavisThe1st wrote:
           | Software exposes (at least) two faces ... the user facing one
           | ("habitability") and the (future) developer facing one
           | ("building").
           | 
           | GoF approaches from the "building" side since it was created
           | by and for programmers. Another way of thinking about this is
           | that GoF is describing the experience of the design as a
           | developer having to "inhabit" it, rather than a user's
           | experience of the same.
           | 
           | That's why Alexander is not a particularly useful book for
           | _builders_ , since it focuses on the user's experience of the
           | design when inhabiting it, rather than the constructors'
           | experience of the same design when creating or modifying it.
           | 
           | A version of GoF that focused on the user experience would be
           | a completely different book than the one we know.
        
       | estebarb wrote:
       | I have a bug/hate relationship with the visitor pattern: how do I
       | implement it without causing a stack overflow in languages
       | without tail recursion optimization? It always ends up in a loop
       | with a lot of ifs inside.
        
         | baq wrote:
         | with a 'for' loop
        
         | ghusbands wrote:
         | Trampolines can help with that.
        
       | dgreensp wrote:
       | You don't come across visitors every day, and they aren't
       | something to reach for instead of a simple switch, but if you are
       | writing a Webpack or ESLint plugin or something like that, you
       | might encounter them for traversing an AST. It's not just an OO
       | thing, either.
       | 
       | I learned the visitor pattern in pretty ideal circumstances: from
       | a classmate, back in college, when we were writing a compiler
       | together for a class project, and it was just what we needed.
       | 
       | The right conditions for a visitor are when you have a data
       | structure with many different "node" types, and the traversal
       | logic is not simple. In addition, you will be doing many
       | different traversals. You might also be doing transformations on
       | the structure. You might have a library running the
       | traversals/transformations provided by the code that uses the
       | library. There could be correctness or memory management
       | considerations involved that you don't want the client to have to
       | worry about. You might want to "pipeline" multiple
       | transformations, doing several manipulations in one traversal, in
       | an efficient way. Common traversals might care about only certain
       | types of nodes, like a particular lint rule might just be looking
       | at string literals or references to global variables, but it
       | needs to walk the entire syntax tree of a file of source code.
        
         | mhh__ wrote:
         | The problem with the visitor pattern is that it hides the
         | traversal which can be good for some things but then lead to
         | weird bugs (e.g. imagine walking a tree with a buffer in the
         | visitor, then the thing that runs your visitor runs the nodes
         | more than you expect and so on) e.g. I've seen bad code slip
         | past a compiler because the visitor didn't realize it was being
         | run on every node in the tree so it never actually found the
         | pattern it was trying to match.
        
         | dapperdrake wrote:
         | Someone else beat me to it. Will answer anyway.
         | 
         | Had a very similar experience to yours with one important
         | exception. The visitor pattern is relevant for traversing ASTs
         | in _single dispatch_ languages. Most notably, this includes
         | Java and C++.
         | 
         | The alternative concept is multiple dispatch and it is way more
         | anti-fragile.
         | 
         | This is where the next step in college literally was to learn
         | the concept of cargo-culting. The concept of multiple dispatch
         | is _considerably_ more useful than single dispatch.
         | 
         | Many years later the realization hit, that OOP, singletons, DB
         | mocking and monads are the hard parts [1,2]. Then you can even
         | skip multiple dispatch in favor of low latency. C++ wants this
         | but its ecosystem has _no idea_ (as in zero) of how to get
         | there [3,4,5,6] (-Ofast -fno-fast-math). Then the  "Patterns"
         | (capital P) melt away.
         | 
         | On a semi-related note, it seems worth pondering whether strict
         | typing requirements and/or a missing garbage collector make
         | macro programming and AST work so hard in non-lisp languages.
         | Think I read somewhere that some people considered Rust macros
         | hard. Sounded like they were harder than they should be, which
         | means that the language designers piled on incidental
         | complexity. Macros are difficult enough as it is. And worth it.
         | Even Python pilfered the "with" syntax.
         | 
         | [1] http://somethingdoneright.net/2015/07/30/when-object-
         | orienta...
         | 
         | [2]
         | https://lobste.rs/s/vbivyq/synchronous_core_asynchronous_she...
         | 
         | [3] https://moyix.blogspot.com/2022/09/someones-been-messing-
         | wit...
         | 
         | [4] https://news.ycombinator.com/item?id=32738206
         | 
         | [5] https://matklad.github.io/2023/11/15/push-ifs-up-and-fors-
         | do... Note, that Fortran and Functional Analysis got this right
         | _ages ago_. Excuses are now few and far between. All of use are
         | late to the party.
         | 
         | [6] https://news.ycombinator.com/item?id=38282950
         | 
         | Edit: typo
        
           | PoignardAzur wrote:
           | What do you mean by "multiple dispatch"? What's an example of
           | a multiple dispatch program?
        
             | Jtsummers wrote:
             | https://en.wikipedia.org/wiki/Multiple_dispatch
             | 
             | It means dynamic dispatching based on the types of multiple
             | arguments not a single argument.
        
         | xelamonster wrote:
         | I wrote some Typescript AST walking code recently, and could
         | not for the life of me figure out what the advantage of the
         | visitor pattern was here. It seemed to only overcomplicate the
         | implementation, made it much more difficult to track where you
         | were in the tree and target specific nodes and seemed much
         | easier to just call getChildren and iterate.
        
       | mhh__ wrote:
       | I've seen it said that Alexander working on this stuff just at
       | the moment of peak OOP-timism is a great shame/tragedy. I'm
       | inclined to agree.
       | 
       | Functional purists like to glibly say that their patterns are
       | discovered rather than invented. I used to Pooh Pooh this a bit.
       | Then I had to write a class just to have a function to call.
       | They're right.
        
         | carlmr wrote:
         | > Functional purists like to glibly say that their patterns are
         | discovered rather than invented.
         | 
         | As someone who suffered through the whole OOP-madness I found
         | this presentation so refreshing:
         | https://fsharpforfunandprofit.com/fppatterns/
        
       | smallnamespace wrote:
       | I disagree with the main thrust of the article, that the Visitor
       | pattern is primarily a primitive way to do pattern matching.
       | 
       | For one, the Visitor pattern (as written in the article) fails to
       | fulfill a core feature of Rust pattern matching, which is having
       | a compact language to describe when something matches based on
       | values, as opposed to the concrete type.
       | 
       | For another, you could equally well say that if-else blocks or
       | ternary statements are also primitive pattern matching, if you're
       | willing to stretch things that far.
       | 
       | In my view, the core reason people reached for the Visitor
       | pattern in the past is that old Java didn't have convenient
       | lambdas (anonymous functions). Visitors let you create an
       | interface that mimics functional map.
       | 
       | Newer versions of Java have made lambdas much more convenient, so
       | there's less motivation to reach for the Visitor pattern. You
       | also saw this development in C#, where delegates were a language
       | feature that often obviated rolling your own Visitors.
        
         | acchow wrote:
         | How do you achieve multiple dynamic dispatch with lambdas?
         | Without pattern matching, don't you need the visitor to
         | incrementally dispatch?
        
       | Robin_Message wrote:
       | The visitor pattern is a way of getting multiple dispatch in a
       | language with single dispatch.
       | 
       | It can also be seen as a way of encoding a functional solution to
       | the expression problem in an OO language, which is useful when
       | you have a set of objects and want to make it easy to add new
       | operations to them, not create new types of objects.
        
       | miningape wrote:
       | I think the visitor mimics pattern matching, but it really
       | behaves/feels different. I think the visitor is really closer to
       | a bridge from OOP land into FP land.
       | 
       | In FP its difficult/impossible to add new types but simple to add
       | new mappings/pipelines for those types.
       | 
       | In OOP its easy to add new types but difficult/impossible to add
       | new mappings/pipelines for those types (try transforming a POJO
       | without lombok @Builder and @Value).
       | 
       | The visitor pattern (an OOP concept) allows us to _more_ easily
       | add a new mapping /pipeline for a set of related types. Normally
       | in OOP this would involve adding a new method to a base class,
       | implementing these new "mappings" in each subclass. With the
       | visitor instead you just implement the new visitor, this allows
       | the logic for this particular "kind" of mapping (visitor) to be
       | grouped together, rather than represented by a method on a base
       | class.
        
         | dapperdrake wrote:
         | You seem to have given this some thought.
         | 
         | Honest question: What is your current estimate of how much the
         | OOP side is impacted differently by Composition vs.
         | Inheritance, especially when contrasted with FP?
        
           | miningape wrote:
           | Honestly, for all the OOP talk about composition its rarely
           | done well and afaik no major OOP language supports that
           | paradigm, so it's a bit tough to say, as a really good take
           | on it from a language level could affect my opinion a bit.
           | Java 21 seems promising in this regard with switch + record,
           | but the legacy java layer seems to drag it down.
           | 
           | Currently I'm not entirely convinced composition is a silver
           | bullet that will save OOP and from what I've seen it is also
           | somewhat antithetical to OOP - you are constructing a bunch
           | of "has-a" relationships and expecting it to relate to
           | behaviour (generally done by inheritance / "is-a"). So in
           | "saving" OOP composition will likely kill it.
           | 
           | Instead I think we'll see more (especially newer) languages
           | move towards FP by introducing composition (like rust /
           | golang interfaces). I think it's because composition maps
           | quite well to the FP concept of a composite data type (like a
           | struct / tuple) as well as function composition. But at the
           | same time I think we'll see a lot of in-between languages
           | (again rust / golang) where we take some stuff from
           | procedural+oop and mix it with a bit of functional
           | programming (especially in data pipelining areas). Similar
           | (but opposite) to how Java 8 introduced the streams monad.
        
       | andybak wrote:
       | > because that's a major L
       | 
       | What's "L"? Am I getting old?
        
         | Bjartr wrote:
         | "Loss" I believe, contrast with "W" for a win
        
         | IncreasePosts wrote:
         | If that's you in your twitter handle pic, then yes, you are
         | definitely getting old.
        
       | gorkempacaci wrote:
       | If I may nitpick, the call to children's accept methods should be
       | in the visitor, not the parent. Imagine you're writing an
       | XMLGeneratorVisiter. The visit method for the parent would print
       | <parent>, call child accepts, and then print </parent>. If you do
       | it the way it is done here you lose the control on when the
       | children are visited.
       | 
       | Also, the point of the visitor pattern is not just pattern
       | matching/polymorphism. Of course you could do it with
       | polymorphism or conditionals or whatever. But the visitor pattern
       | reduces coupling and increases cohesion. So you get a more
       | maintainable, testable code base. Pattern matching with a candied
       | switch doesn't give you that.
        
       | svieira wrote:
       | Anyone who is interested in pattern matching and the visitor
       | pattern (and the benefits of various encodings of state and
       | behavior) who hasn't seen them should check out:
       | 
       | * Li Haoyi's _Zero-Overhead Tree Processing with the Visitor
       | Pattern_
       | (https://www.lihaoyi.com/post/ZeroOverheadTreeProcessingwitht...)
       | 
       | * Noel Welsh's _Uniting Church and State: FP and OO Together_
       | (https://noelwelsh.com/posts/uniting-church-and-state/ or
       | https://www.youtube.com/watch?v=IO5MD62dQbI is you like video)
       | 
       | * Bruno C. d. S. Oliveira and William R. Cook's _Extensibility
       | for the Masses Practical Extensibility with Object Algebras_
       | (https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)
        
       | michielderhaeg wrote:
       | This reminds me of when I was still in university. During our
       | compilers course we used the "Modern Compiler Implementation in
       | Java" book. Because I was really into FP at the time, I instead
       | used the "Modern Compiler Implementation in ML" version of this
       | book (which was the original, the Java and C versions were made
       | to make it more accessible). We noticed during the course that my
       | version was missing a whole chapter on visitors, likely because
       | ML's pattern matching made this kind off trivial.
       | 
       | One of the other students made a similar remark, that software
       | design patterns are sometimes failures of the language when they
       | have no concise way of expressing these kinds of concepts.
        
         | delusional wrote:
         | > that software design patterns are sometimes failures of the
         | language when they have no concise way of expressing these
         | kinds of concepts.
         | 
         | This is _the_ most common comment you hear in any language
         | design discussion, and it's so boring. Java doesn't have
         | visitors because it's the best the language could come up with.
         | It has visitors because of a particular dogmatic adherence to
         | OOP principles. A fundamentalist reading that says you're never
         | supposed to have any control flow, only virtual method calls.
         | Visitors come from the same place as Spring and the infamous
         | AbstractSingletonProxyFactoryBean.
         | 
         | Java is perfectly capable of expressing an enum, and you can
         | easily switch on that enum. The limitation that you have to
         | express that enum as a type hierarchy, and therefore encounter
         | the double dispatch problem is entirely OOP brainrot.
        
           | nine_k wrote:
           | Indeed there should be no control flow, only computing values
           | of function application :)
           | 
           | Indeed, this is also absolutism, just of a different kind,
           | that proved to be somehow more fruitful in the application
           | programming domain. And, to my mind, it's mostly because
           | functions can be pure and normally return a value, while
           | methods are usually procedures mutating some hidden state.
           | 
           | OTOH in a different domain, system programming, control flow
           | is a good abstraction over the close-by hardware, mutable
           | state is all over the place in the form of hardware
           | registers, and it's more fruitful to think in terms of state
           | machines, not function application.
           | 
           | If a tool forced you to do repetitive _irrelevant_ motions,
           | it 's just a wrong tool, you need to find or create a better
           | one.
        
         | nine_k wrote:
         | <<Design patterns are bug reports against your programming
         | language>> (Peter Norvig)
         | 
         | This is pithy, but only applies to some rote design patterns
         | which you have to code every time because the language lacks a
         | good way to factor them out.
         | 
         | Wider-scale patterns often express key approaches _enabled_ by
         | the language, and they can 't go onto a one-size-fits-all
         | library or language feature, because you tailor their
         | implementation to your specific needs. They are less like
         | repetitive patterns of a brick wall, and more like themes or
         | motifs that keeps reappering because they are fruitful.
        
       | digging wrote:
       | I got about halfway before completely losing the thread as I had
       | no idea what was being said, unfortunately.
       | 
       | Is "visitor" pattern ever defined in this article?
       | 
       | Is there anything in this article explaining the assertion
       | "Design Patterns Are Temporary, Language Features Are Forever"? I
       | couldn't penetrate the specific example being discussed.
        
         | syncsynchalt wrote:
         | This is an article about design patterns (in particular the
         | 1994-200x craze for them after the Gang of Four book was
         | published). Having emerged from that era might be table stakes
         | for understanding this article.
         | 
         | Visitor pattern is a code inversion where inheritance and/or
         | function overloading fills in for a missing language feature
         | (concise runtime pattern matching on types). It has its pros
         | and cons but seemed like opening your third eye circa 1995.
        
       | kazinator wrote:
       | Language features are not forever.
       | 
       | Language features can be deprecated as obsolescent and after a
       | period of obsolescence, removed.
       | 
       | "implicit int" declarations are no longer a C language feature.
       | The (void) parameter list is now obsolescent; as of C23, () means
       | the same thing as (void), like in C++.
       | 
       | Design patterns can end up in libraries. But library and language
       | features are the same. A popular library which everyone uses
       | cannot be thrown out overnight any more than a language feature.
        
       | weinzierl wrote:
       | Unfortunately, I can't remember where I heard that bon mot: _"
       | Every design pattern is a bug report against your compiler"_
        
       | syncsynchalt wrote:
       | The author took the thesis in one direction, but there's another
       | interesting way to think of it.
       | 
       | C++ is an epitaph for every discarded concept in OO design.
       | Diamond inheritance, generics to a fault, operator overloading to
       | a fault, the richness of STL types. Those "patterns" are dead,
       | but the language features must continue to be supported.
        
       | vrnvu wrote:
       | I always tell people who obsess over design patterns as clean
       | code and good coding practices to remember that design patterns
       | often indicate a lack of language features.
       | 
       | For example, are you using a Builder? Would you use the Builder
       | pattern if the language had named variables in arguments?
       | 
       | My favorite reference on the topic is Peter Norvig's "Design
       | Patterns in Dynamic Languages" (1996!)
       | https://www.norvig.com/design-patterns/
        
         | davidcuddeback wrote:
         | > _Would you use the Builder pattern if the language had named
         | variables in arguments?_
         | 
         | Yes, absolutely. I see it all the time in the Ruby ecosystem
         | and have used it myself in Ruby. Many times it gets called by a
         | different name. I've seen it in Python and Elixir too.
        
         | nine_k wrote:
         | A builder can allow you create different kinds of objects from
         | a common stem. They don't have to be bags of properties.
         | 
         | Say, SQLAlchemy is all built on chaining builder-like methods,
         | which make one of the finest DSLs that translates to SQL. In
         | the end, you build a representation of a SQL statement, but in
         | no way could that work with named arguments alone.
         | 
         | Instead, consider named arguments as nice shortcuts over
         | curried functions.
        
       | bad_user wrote:
       | > _Now knowing about pattern matching, this is how I came to
       | realise that the visitor pattern was pattern matching in an OO
       | way._
       | 
       | The programming language needs to be very expressive to replace
       | the visitor pattern with pattern matching. For example, you need
       | GADTs. The cool thing about static languages with OOP is that OOP
       | can hide a lot of type-level complexity. Also, in languages with
       | runtimes optimized for virtual calls (e.g., the JVM, V8), pattern
       | matching can have less performance than the visitor pattern,
       | despite pattern matching now being a reality on the JVM at least
       | (e.g., Java, Scala have pattern matching).
       | 
       | The difference between pattern matching and the visitor pattern
       | is the difference between tagless-initial and tagless-final
       | encodings, or data versus Church-encodings. And as a software
       | engineer, it's good to be aware of their strengths and
       | weaknesses.
       | 
       | People making this claim (that design patterns are just missing
       | language features) always mention Visitor, but stop short of
       | mentioning other design patterns, such as Observable/Listener,
       | but also, the even less obvious: Monad or the Free Monad (i.e.,
       | still a design pattern, despite being able to comfortably express
       | it in your language).
        
       ___________________________________________________________________
       (page generated 2024-09-09 23:01 UTC)