Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

It's true, as Norvig points out, that newer languages are making old design patterns disappear (slide 10), but new ones are appearing at the same time, and he mentions that too. The simple design patterns like iterators die when iterators become intrinsic, but now we get higher level patterns built out of the small patterns now in the background.

I wonder if it's more about how much code you have to write. Patterns are patterns because you have to write some code to make them, i.e., they're not already there in the language, and as soon as you have a generalized class that actually solves a structural problem, you have a pattern.

I suspect that powerful and expressive languages won't make patterns go away, we'll just get powerful and expressive patterns.



Lisp macros give you the ability to abstract away patterns ad infinitum. There's no hard ceiling there. So you can make patterns go away with enough expressive power.


Can you give us a couple of examples of how lisp macros "abstract away" patterns?


Take the example from [0]:

> Also - there are such "nonexciting patterns" in OOP languages as well, and they are much less often abused. For example "a switch inside a while" pattern. Which I think I will need to name "StateMachinePattern" to make it cool and stop people refactoring it into strategies and stuff.

> There is value in one-screen definition of the whole machine, instead of separating it into 6 files.

In Lisp you could create a macro, say, define-state-machine, and use it like that:

  (define-state-machine :my-state-machine
    ((:state-1
      (do-something)
      (do-something-else)
      (if (condition)
          (transition :state-2)
        (transition :state-3)))
  
     (:state-2
      (do-something)
      (transition :state-3))
  
     (:state-3
      (do-something)
      (when (should-quit)
        (quit-state-machine)))))
This macro could easily expand to a "switch inside a while" (or, possibly, to a "let over lambda over cond", or maybe even into a series of low-level tagbody and go constructs). The resulting abstraction is clean and communicates its meaning well.

--

Different example - when writing macros in Common Lisp, there are two things one usually has to be wary of - unwanted variable capture, and unwanted re-evaluation. So you might end up manually assigning gensyms[1] to avoid variable capture, and manually creating lexical environments to evaluate passed forms only once. But you can also abstract it away! For instance, with with-gensyms and once-only macros from Alexandria[2]. Or you could build something like defmacro! described in Let Over Lambda[3], i.e. a macro that automatically code-walks your macro definition and extracts variables named g!foo and o!bar to apply gensyms and once-only to them, respectively.

--

Those are only two examples, but the general idea is - whenever you see yourself repeating the same boilerplate in similar places to express a concept, you can wrap that boilerplate inside a macro and make the compiler generate it for you. Since Lisp makes all of its capabilities available for you during macroexpansion, you can continue this until you're satisfied that your code is readable, boilerplate-free and clearly expresses your intentions.

--

[0] - https://news.ycombinator.com/item?id=11730248

[1] - gensyms are symbols generated on demand, that are guaranteed to have no possible collision with anything ever

[2] - https://common-lisp.net/project/alexandria/draft/alexandria....

[3] - http://letoverlambda.com/index.cl/guest/chap3.html#sec_6


I use this sort of thing in Tcl ( for path-dependent reasons ) A Whole Lot. You can have arrays, indexed by state, and use the "eval" operator to "eval $fsm($state)"

fsm is an array of strings, indexed by whatever.

I have made dynamic state machine generators this way, although it gets kinda disorienting.

The tactical advantage to Tcl (for me) is that it has serial ports and sockets as first-class objects with essentially identical semantics. I work in the embedded space, and this seems 1) unusual and 2) a very nice thing to have to write comprehensive test rigs.

I rather like it better because it's all in the "string" & "list" domain rather than the lambda domain. I really should try this in Lisp just to see how wierd it gets.


Eh, lisp macros are nothing more than a way to avoid quoting your symbols. Functions are more clear, and do not try to hide what is going on. Honestly wish it was like this: (defun 'my-adder '(x y) (+ x y)) ;; emacs lisp.

I'm a lisper and clojure user. Those are my main languages. Don't get the hype for macros, but lisp is the best.


The main difference is that unless you are using an interpreter, you can apply macros at compile-time. Also, without macro you build forms which you must then evaluate: eval cannot access the lexical environment. You need to systematically pass an environment, either at runtime in an interpreter or at compile-time: more boilerplate.

To summarize, meta-programming is a recurrent pattern that was abstracted with macros. The defmacro macro itself produces functions like you want to use, except that it integrate them with the macroexpansion facility offered by the environment.


I'm happy to be part of a community, where even when I spew out something ignorant there is a clear explanation that makes sense. Thanks for your answer.


I think this perfectly shows that design patterns are just that patterns. They're not goals but they aren't really tools either. They're just what you see when you compare different code bases because similar problems will provoke similar solutions even from different people.

Design patterns only become problems when people learn about them and try to apply them without learning or understanding how they came to be.


Or, apply them even though they likely will never need them...

One pattern implementation that I always hated, and still do was MS Enterprise Library's Data abstraction. I worked on one project that targeted three different databases for Enterprise customers, which was a great fit. That's the only time I worked with EntLib where using it was better than just using other tooling for the specific database directly.

In the end, sometimes people get used to a given framework, and don't stop to think if they really need that framework for what they're working on. It's one of the things I really like about the node ecosystem, though sometimes that goes too far the other way. That said, I tend to rail against certain patterns as they don't have much place in a given language/platform.

It tends to be something that comes with age/experience. But not always, I've known plenty of older devs stuck in framework/pattern rutt.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: