Does Functional Programming Replace GoF Design Patterns?
stackoverflow.com
stackoverflow.com
The truth is that modern OOP (even in Java) doesn't many of the patterns in the GoF book any longer. That's not much of a surprise though: GoF is 18 years old. Approaches to problems have changed and the patterns used are a bit different.
Idiomatic Haskell is full of design patterns. Here's a few of the popular ones: monads, composition (of functions, not objects), maybe. When I stopped use Haskell actively, they were just getting a handle on the overuse of monads. Pattern overuse had occurred, just as in Java. It seems that pattern popularity follows the Hype Cycle (http://en.wikipedia.org/wiki/Hype_cycle)
>The truth is that modern OOP (even in Java) doesn't many of the patterns in the GoF book any longer.
Oh how I wish I shared your optimism. You still need Visitors and Factories and Singletons in Java, because the language still doesn't provide the features that replace them. At least with java 8 we'll finally be getting lambda.
There are cross-cutting concerns in software development that don't have an obvious technical solution. Modular code is one of them. There's no logical reason for us to organize code in such a roundabout way, it's only to help us measly humans reason about it more efficiently during the process of software development. So, there's unlikely to be a way to unproblematically translate that process into something logical.
I think there's this constant tension in language design between giving you the power to do really cool things (python) and the constraints to stop you screwing up (java). But I think that more modern languages are getting further down the funnel, combining the advantages of both.
lineLengths = map (length . words) . lines
as compared to lineLengths text = map length (map words (lines text))
I don't think that monads are a design pattern either (in this I agree with Tony Morris [1]). Monads are a unified method of doing contextual computations. They are far too general to be a design pattern in themselves (how many design patterns do you know that encompass failure, error handling, nondeterminism, probabilistic computation, logging, backtracking search, mutable state, asynchronous computation, continuations, I/O...)Monads should be more accurately thought of as an interface that is implemented by many types (in Haskell terminology, a class that many types are instances of).
I am not saying that there aren't any design patterns in Haskell - just that function composition and monads are not examples of design patterns. Instead, common design patterns include
* Writing tail-recursive, accumulating helper functions where you'd use a for loop in an imperative language:
sum xs = go 0 xs
where
go result [] = result
go result (x:xs) = go (result + x) xs
* Using strict left folds to accumulate values sum = foldl' (+) 0
* Using runStateT and its cousins to tame stateful computations sum xs = execStateT (go xs) 0
where
go [] = return ()
go (x:xs) = modify (+x) >> go xs
* Using a record of functions to encapsulate related functions that might be given different implementations (like a lightweight version of classes) data Lens a b = Lens { get :: a -> b, set :: a -> b -> a }
* Using libraries like Conduit or Pipes to structure your program as a flow of data through componentized parts.Then there are 'large scale' design patterns, like
* Structuring your code in three parts: (i) data type declarations, (ii) pure functions operating on the data, (iii) a thin I/O wrapper to glue the pure functions together.
* Using the MonadIO, MonadState, MonadWriter etc classes to keep your code implementation agnostic.
To re-iterate: I agree with you that functional languages (like Haskell) still have design patterns. But I don't think that monads or function composition are good examples of functional design patterns.
None of those are design patterns in haskell. They may well be design patterns in other functional languages (probably only monads really), but in haskell they are very basic, fundamental features of the language. That is like saying classes and variables are design patterns.
We have a definitional disagreement: this is a bad definition. Here's what I think, straight off page 3 of GoF:
> A design pattern names, abstracts and identifies the key aspects of a common design structure that make it useful for creating a reusable [object-oriented] design.
Composite is a great counter example to your definition of a design pattern, because it is an example of an important pattern (IMO the most important in GoF) in which no special implementation is required.
PS. Calm down and stop accusing people on the internet of bad faith
P.S. If you want people to think you are making a good faith attempt at discussion, making passive aggressive douche notes like this is counter productive.
They everything is design pattern. Except it is not.
I don't interested in what the GoF says, but in the FP world this is not pattern. It's just an another function actually
(.) :: (b -> c) -> (a -> b) -> a -> c
(And referential transparency, lambda calculus, higher-order functions and HM type inference are not design patterns)You can indeed apply the same pattern (fe visitor) in Python, Java, C++, Go. This is also the reason why it isn't that difficult to move from one OO language to another.
Functional programming languages fall within another paradigm. You do have patterns like 'fold', 'map', or 'nexus' but in general, they tend to be very small. This is because functions allow both more strict and more flexible composition than objects. Again, once you know Lisp, or Scheme or ML you can jump from one language to another, although from Scheme to Haskell will be a larger jump (purity & laziness) than from Scheme to Erlang.
Two final remarks: ) The declarative paradigm (Prolog, Mercury, ...) seems to be out of flavour these days.
) Every self respecting developer should have mastered at least 1 language in every paradigm.
I enjoyed this series of articles published some years ago making the case that many of the useful design patterns are built into Perl already:
http://www.perl.com/pub/2003/06/13/design1.html
http://www.perl.com/pub/2003/08/07/design2.html
http://www.perl.com/pub/2003/08/15/design3.html
The take-away point being "If a pattern is really valuable, then it should be part of the core language". Perl, of course, has very strong functional features, as described in Higher Order Perl:
I think this is what has caused so many misunderstandings people have about Perl over time. Perl has so many of such patterns built which solve many problems with default. In fact most people fall in love with Perl for these reasons. To understand why that is important you need to recognize such patterns in your problems, if you don't see any you will feel the solutions contained in a language as rubbish.
The entire Unix command line toolset is entirely about this thing. Unless you learn how to reduce a problem to a text processing problem, you will never learn to appreciate it.
See Steele's "Growing a Language" talk.
'nexus'?
The only relevant hit on the first page of the google results for those 3 words together is actually a link back to your post https://www.google.com/search?q=map+fold+nexus
It comes from "Pearls of Functional Algorithm Design" (R.Bird) It's basically a tree with shared nodes.
ftp://kawais.org.ua/incoming/Bird%20-%20Pearls%20of%20Functional%20Algorithm%20Design%20(Computer%20Science)%20-%202010.pdf
This is actually a pattern that I've been looking for a name for, for years! And you've actually sold the OP's featured answer for me by making me use that word ("pattern"), notice the concept's genuine applicability to this context, and realize it's not a Java-spawned vulgarity.
The O.P. is about when the design patterns are needed, not what you need to implement them.
Incidentally, the visitor pattern doesn't require OO for implementation, it requires a type system and single dispatch, i.e. choosing a method based on the run-time type of its first argument. Python doesn't have single dispatch. So the visitor pattern doesn't really work in Python, you essentially have to code a huge number of branches, e.g. n switch statements, each with n cases, in order to dispatch n^2 ways.
Did K&R fail to deliver because not everyone writes C for everything today?
Outside this interview (I succeeded), knowing DP has never helped me to be a better developer.
The patterns in the GoF are often encountered in libraries written in object oriented languages that do not use much templates or parallelism. There were many in Java library. I think I have not found any of them in the C++ standard library.
Now, I see singleton used everywhere because people are too lazy to design correctly program startup. When tasking is involved, incorrect use of singleton causes very nasty bugs (a crash every month).
For me, singleton is a dangerous pattern (almost as bad as global variables, sometime worse), the others patterns just happen to be the best way to do some common things. Identifying them is never a problem, having a name for them is useful.
The main proof of GoF failure is that we have never seen all the new books with new DPs. I still wait for a new list of common and language agnostic design patterns. Until that, DP remain for me a good example of failure.
C has exceeded all its expectations, what is the point of your comparison ?
If monads are design patterns, then why not rings (Num typeclass), which allow to use addition and multiplication in different contexts? If function composition is a design pattern, then why not function application? If chaining with '>>=' is a design pattern, then is using a semicolon a pattern too? If 'fold', 'map' are patterns, then why not 'sin' and 'cos' are? What is the difference? There's a "pattern" that after sine you put a number; there is also a "pattern" that after a fold you put a function. Thinking that higher-order functions are special is contrary to the spirit of functional programming. Functions and numbers are the same class of citizens, and a function taking a function should not be treated differently from one that takes a number.
Bare names of often-used library functions or language constructs should not be considered patterns, unless they occur as parts of repeating snippets. In this case, you should refactor or encapulate. If you cannot, this is a language flaw. It sucks if you have to type "x = new T(); try { ... } finally { delete x; }" each time. If your language allows to write "using (x = new T()) { ... }" then the pattern is eliminated.
Perhaps there are some "design patterns" in FP, but examples I find often are very weak. One thing that I would count as a pattern is "lift . lift . lift" used in Haskell in monad transformers.
Thinking that objects are special is contrary to the spirit of object-oriented programming. Objects and numbers are the same class of citizens, and a method taking an object should not be treated differently from one that takes a number.
Would you also argue that there are no (or very few) design patterns in object oriented languages?
putStrLn :: MonadIO m => String -> m ()
ask :: MonadReader r m => m r
modify :: MonadState s m => (s -> s) -> m ()
Now you can define instance (MonadTrans t, MonadState s m) => MonadState s (t m) where ...
so that any monad transformer applied to a state monad is automatically another state monad. Win!The short answer is - no Java - no problem.)
The longer answer is:
Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of CommonLisp.
"Including Common Lisp." - Robert Morris
It's a bit interesting that Norvig talks (some) about design patterns in Lisp, since the whole point of Lisp, and particularly Lisp macros, is to be able to rewrite the language itself, so if you find yourself wanting to use a design pattern, you just write the macro that implements it for you.
For example, the Visitor pattern can be naturally replaced with map and fmap in Haskell, which are far more powerful.
you can do V with F
to which the reply was no, you can't do V with F
and the subsequent reply was but you can do V with M
and I pointed out that F does not imply V. Your current reply is Clojure has both F and M and can do V
which is obvious, since M implies V already, but still unrelated to what F can do. class Read r where read :: String -> r
(Let's ignore parse errors for our purposes here)Haskell supports return-type polymorphism. Clojure, being dynamically typed, cannot in general support this kind of overloading.
I think it's a trade-off though. Can Haskell dispatch on a value rather than a type?
The Monad class actually depends on it too:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
The type of "return" can only be expressed with return-type polymorphism.This means you cannot really implement Haskell-like monads with dynamic typing. You have to implement them in a less general way.
As for dispatching on runtime values, that is ordinary pattern matching:
f (RuntimeValue1 x y) = ... case 1 ...
f (RuntimeValue2 x y) = ... case 2 ...Ahh, yeah I've heard this before. It totally slipped my mind.
>As for dispatching on runtime values, that is ordinary pattern matching
Can you extend the patterns of a function at runtime (or even across modules)?
Since Haskell does not have sub-typing, everything has the same runtime type. However, you can easily combine different types into a single algebraic data type and then dispatch by pattern matching against that type.
This probably isn't exactly the same as double-dispatch in other languages, but it seems to be very similar and to serve the same purpose.
Here is the good explanation: http://www.haskell.org/pipermail/haskell-cafe/2009-June/0623... Haskell people are good at math
I guess it's all education in the end. At least now I appreciate the value of the tools I have.
However, it was also about iterating an entire data structure (in the GoF example, a document hierarchy).
This creates confusion as some emphasize the former (multimethods! pattern matching!) and some emphasize the latter (fmap! map!).
"Finally reading Design Patterns. The cross platform WYSIWYG editor used as an example doesn't "smell" like something actually implemented."
https://twitter.com/ID_AA_Carmack/status/257683318907215873
I had the same impression. How to trust the authors with such unbelievable example.
That said all the programs that used design patterns always seemed too synthetic for me, as if the main purpose of the programs were generality. I learned that often it is better to keep other goals in mind, like practicality, simplicity or maintainability.
[1] https://twitter.com/TonyAlbrecht/status/257685852153581569
http://doctrina.org/Javascript-Function-Invocation-Patterns....
Had to giggle a little over this silly discussion of FP and are there design patterns or not. A strategy is the same intent whether you use a enclosed function or an anonymous object as the strategy. Remember the old computer programmer adage. Objects are the poor man's closure, and closures are the poor man objects.
This to me belies that the author doesn't actually do much programming with monads. They're more like the core abstraction of an interpreter or a sequencing algebra than a design pattern for global state.
Considering functions s -> (a, s) as "global state" is closer to a "design pattern", though.
No, monad is a monoid in the category of endofunctors ©
Jokes aside, there are monads for function application, for software transactional memory, for prolog's backtracking, for call/cc, for quantum computations and for running BASIC programs. And many more
Can you stop saying “design patterns” please?
http://stackoverflow.com/questions/11339637/functional-desig...
For instance, say you have a Log object that has 15 or so Log methods for different severity levels and signatures and you just want to write something that prepends something to anything that gets logged.
If you split this to "something that applies a function to any message passed to the log" and "a function that transforms a message" you get something that's certainly better if you implement more than one transform and probably better in the case of just one because the repetitive boilerplate is physically separated from the "business end".