Design patterns are mostly crutches for languages that have pretty weak expressive power. In a powerful language, you don't need most of them.
See also: http://norvig.com/design-patterns/design-patterns.pdf, slides 9 and 10.
Design patterns are mostly crutches for languages that have pretty weak expressive power. In a powerful language, you don't need most of them.
See also: http://norvig.com/design-patterns/design-patterns.pdf, slides 9 and 10.
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.
> 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
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.
I'm a lisper and clojure user. Those are my main languages. Don't get the hype for macros, but lisp is the best.
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.
Design patterns only become problems when people learn about them and try to apply them without learning or understanding how they came to be.
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.
Unfortunately, a lot of people make the mistake of seeing the GoF list of design patterns as being the holy scripture of patterns, when it's merely an enumeration of some patterns that proved useful for a particular language in particular contexts. The concept itself (of looking at design at a level above mere algorithms and data structures) is hugely important, it's unfortunate that it suffers from a bit of the "kleenex" effect, instead of being seen as a general purpose concept with wide ranging applicability.
That's a design pattern for optional arguments straight out of ISO C.
[0]http://stackoverflow.com/questions/7828072/how-does-haskell-...
forall z. (a -> b -> c -> ... -> z) -> ZipList z
This is gotten by taking your `list_a :: [a]` etc. and writing: \f -> f <$> ZipList list_a <*> ZipList list_b <*> ZipList list_c <*> ...
You can of course also use Haskell pair-stacks to do all of this, storing (a, b, c, ...) as the type (a, (b, (c, ... ())))...For example, here's what I've written so far for the Visitor pattern:
https://gist.github.com/drostie/818c54ca5c8182143699a72da986...
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.
I never really saw design patterns as a reference manual for how to solve problems (as I think some people do/did). Instead, I saw it as a shared vocabulary.
For example, if I say that X is a factory for Y or an iterator over Z, you know exactly what I mean.
In Haskell even the compiler knows that there are common elements to all instances of the monad pattern---they can reference a common typeclass. (But even if you don't implement an instance of the typeclass, you might still have implemented the monad pattern, but just don't realize it.) In eg JavaScript it's all by convention only.
But even in Haskell, the compiler does not enforce all monad laws, yet.
A more common example, but perhaps invisible today, is the `function' pattern. In most assembly languages, functions are a convention only, and you have to manipulate your call stack somewhat manually.
In almost all languages anyone is using these days, that's done automatically. Functions are not `invisible' but they are citizens with more right, ie the compiler / interpreter knows about them, you can give them a name, and in some more modern languages even pass them around, or _not_ give them a name.
Some patterns do become invisible as you say.
`Factories' are one that only exists because of weird restrictions in eg Java. They are invisible in Python.
In Haskell for different reasons we have a similar solution where we define extra functions to return our create our objects instead of using constructors directly. Instead of factories, people call these smart constructors (https://wiki.haskell.org/Smart_constructors).
Smart constructors in Haskell could go away with a stronger type system like in Agda. (Yes, there are languages with stronger types than what Haskell has.)
Functions are a design pattern in assembler, but a language feature checked by the compiler in more advanced languages.
Classes are design pattern in C (called ADTs), but a language feature in all OOP languages.
Generalizing the concept: A design pattern is something the programmer has to check himself/herself for correctness, instead of being automatically checked by the compiler or interpreter.
You don't hear design patterns mentioned a lot with simple toolchains like that supporting golang.