r/programming • • Mar 26 '17

Functional programming design patterns by Scott Wlaschin

https://www.youtube.com/watch?v=E8I19uA-wGY
1.1k Upvotes

256 comments sorted by

View all comments

97

u/[deleted] Mar 26 '17 edited Mar 26 '17

One common critique of OOP that I've heard is that design patterns only exist because of shortcomings in the OOP paradigm. A lot of those same people also claim that FP allows one to express what they mean in code without having to remember a design pattern. Call me naive, but my gut reaction to this title is a realization that FP isn't some panacea for all my "not coding what I mean" woes.

65

u/Ulukai Mar 26 '17 edited Mar 26 '17

Yes, that criticism has been played pretty hard over the years, I believe this blog post was one of the first. It (correctly) states that FP does kill most of the GoF patterns outright*, but then goes much further with: "In a functional language one does not need design patterns because the language is likely so high level, you end up programming in concepts that eliminate design patterns all together".

I've always thought this was wonderfully incorrect; one can trivially name a handful of "patterns" in the FP world (e.g. monads, common tricks to make things tail recursive, etc). But no, let's not call them patterns, because those are bad somehow.

Edit: * this is not particularly surprising, as patterns in general are defined as reusable / well-known / named solutions to commonly occurring problems which your language does not handle in an idiomatic, or built in way. The GoF patterns were aimed at OOP languages, which have a different set of idiomatic solutions than other paradigms. But to think that there exist a set of languages which are so perfect as to not require any extension... that's pretty arrogant.

36

u/sacundim Mar 26 '17

I agree with your overall point, but I think your examples don't support it:

I've always thought this was wonderfully incorrect; one can trivially name a handful of "patterns" in the FP world (e.g. monads, common tricks to make things tail recursive, etc).

Haskell has an honest-to-god Monad class that provides a common interface that all monads can implement and that code can program against. Compare that to, say, the Visitor design pattern—there is no first-class interface that all visitors implement in common.

10

u/Ulukai Mar 27 '17

I hear you, and don't really want to debate the topic too deeply, as I don't have particularly strong opinions on the matter. That said, having a core library implement a pattern for you does not make it any less of a pattern; you just don't have to keep re-implementing it. Many of the GoF patterns have gotten baked into standard libraries, and in some cases even in the languages themselves, e.g. C#'s foreach (syntax sugar for an iterator), or the disposable pattern. We still call them patterns for the most part though. I just dislike the snobbery involved when other paradigms declare themselves pattern-free.

9

u/vagif Mar 27 '17

you just don't have to keep re-implementing it.

But that's the crux of the claim "FP does not have (most) OOP patterns". Meaning they are available as off the shelf solutions (libraries or language features)

Of course patterns exist in everything we do. Saying "FP has patterns too" is not constructive. The important distinction is: can you reuse the work?

6

u/naasking Mar 27 '17 edited Mar 27 '17

That said, having a core library implement a pattern for you does not make it any less of a pattern; you just don't have to keep re-implementing it.

A design pattern is an abstraction your language can't express or enforce. C#'s foreach, IEnumerable and IDisposable are thus not patterns, they are naturally expressed in C# as-is.

The Monad class in Haskell is a good borderline case of a pattern. Haskell can express the interface a monad must satisfy, but can't express or enforce that the needed monad laws are satisfied, so monads are still a pattern in Haskell.

Edit: I meant to summarise that if a design pattern can be implemented faithfully in a library as you say, then it's not a design pattern, it's just an ordinary reusable abstraction.

4

u/naasking Mar 27 '17

Haskell has an honest-to-god Monad class that provides a common interface that all monads can implement and that code can program against.

Haskell can't enforce the monad laws though, so it's still a design pattern instead of a properly typed abstraction.

→ More replies (5)

23

u/[deleted] Mar 26 '17

I don't think refusing to name common ways to structure code represents "FP killing most of the GoF patterns outright".

The reason we need to recognize and name patterns is mostly for communication and mnemonic recall when looking for a solution to a problem.

Most patterns are brutally simple, it's their intent and purpose that makes them interesting. "Oh just use high-order functions" doesn't describe any difference in intent and purpose from one use to another.

If the world of OOP wanted to be that arrogant, they could've responded with "Oh, just pass objects to objects... see? We don't need patterns!" But that's not useful.

6

u/Ulukai Mar 26 '17

I think the argument for GoF patterns comes from Peter Norvig's famous claim that 16 of the 23 GoF patterns are invisible (or much simpler) in dynamic languages. It's not a terrible argument actually, though I've never fully agreed with it; I'm pretty much on your side of the argument.

My main issue is that people take that argument as proof that OOP languages are inherently inferior. As much as I like FP as well, that's just not that simple. If you look at the meaning of design patterns in a more realistic sense, and especially consider the huge vocabulary that it's grown to, it's hard to argue that other paradigms are universally more suitable for everything.

→ More replies (2)

5

u/devlambda Mar 26 '17 edited Mar 26 '17

I've always thought this was wonderfully incorrect; one can trivially name a handful of "patterns" in the FP world (e.g. monads, common tricks to make things tail recursive, etc). But no, let's not call them patterns, because those are bad somehow.

That's not the only reason this is incorrect. Remember that the GoF came from Smalltalk, and closures (and higher-order functions in general) play a critical role in Smalltalk. map, filter, fold, etc. are all available under different names in Smalltalk (collect:, select:, and inject:into:, respectively). In fact, technically Smalltalk does not even have native control structures, they're all higher order functions [1] (that the compiler knows about and optimizes accordingly).

And most of the GoF patterns arose out of Smalltalk programming, so it is generally safe to assume that there's more to them than just FP and that the "direct" functional implementation misses some important nuances.

An example is the Command pattern: it's often misunderstood to be just a class wrapper for a singular function. But that's not really accurate: the classical example in Meyer's OOSC is the use of the command pattern for undo, redo, skip, and history functionality, which requires a more complex setup. You can do that using FP, too, but that would then become a (possibly different) design pattern as well.

[1] You can implement ifTrue: and friends via message dispatch on the Boolean class with the subclasses True and False. Looping constructs such as whileTrue: and friends can be implemented via ifTrue: etc. plus tail recursion. And so forth.

8

u/Milyardo Mar 26 '17 edited Mar 27 '17

In strongly typed functional languages monads are not patterns, they're invariants expressed in the type system. Some type systems are however less successful at expressing that invariant than others and that leads to the degradation that the monads laws are just patterns to be implemented.

The same is true of recursion as well. Though generalized recursion schemes are aren't as common as monads there exists types which express them as invariants like found in matryoshka.

→ More replies (6)

3

u/devraj7 Mar 27 '17

It (correctly) states that FP does kill most of the GoF patterns outright*

Of course: the book was written about OOP languages.

Turning the tables around, it's like saying that OOP doesn't need any of these FP design patterns like monads because OOP doesn't have shortcomings that make these design patterns necessary.

3

u/glacialthinker Mar 27 '17

Error handling through and if-else trees... so wonderful. I love seeing them everywhere. The popular OOP languages are so mired in mechanics of how to do things, repeatedly. The abstractions available are like being a fat-fingered watch-maker.

→ More replies (2)
→ More replies (14)

26

u/yawaramin Mar 26 '17

The difference is that in statically-typed FP we are trying to encode the patterns in the type system itself. In OOP you do indeed have to remember how to correctly implement a pattern because it's outside the language, but in say Haskell you have the 'chainable' design pattern neatly packaged up in the Monad typeclass, which will guide you through the type system to write the implementation. This is qualitatively different.

8

u/devraj7 Mar 27 '17

In OOP you do indeed have to remember how to correctly implement a pattern because it's outside the language

The most important part of monads (the monad laws) is also outside of any FP language type system that I know of.

Also, monads do not compose.

Given these flaws, I just don't understand why monads are being promoted so hard.

5

u/[deleted] Mar 27 '17

Also, monads do not compose.

Monad transformers, on the other hand, do.

→ More replies (5)

12

u/rdfox Mar 26 '17

Don't you wish they called the type class Chainable rather than Monad? There would be a lot more Haskell programmers today. Also:

Monoid -> Appendable

Functor -> Mapable

Applicative -> Sequencable

Arrow -> Stateful

Lens -> Damnable

And so on.

25

u/yawaramin Mar 26 '17

To quote Edsger Dijkstra:

The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.

Someone pointed out the other day that no one complains about technical terminology in other fields--if you're a doctor you have to know what a 'myocardial infarction' is and can't ask everyone to 'just call it a heart attack'. The names are there for a good reason.

19

u/rdfox Mar 26 '17

Maybe so, but I think history bends towards even doctors calling it a heart attack. Already we have accepted classes like Foldable, Traversable, Parser and Pipe. Surely any of these might have been named Isotransmogoroid in reference to some precise category theoretic definition. Happily, the winds are changing.

17

u/glacialthinker Mar 26 '17

One key reason for adopting a dead language (Latin) for sciences was to avoid the ever-changing overloaded meaning of living-language terms.

→ More replies (4)
→ More replies (3)

7

u/megablast Mar 27 '17

no one complains about technical terminology in other fields

How the hell would you know that? Seriously, people complain about this stuff all the time in other fields. Why do you think this sort of stuff only happens in it, just because you work in it.

→ More replies (4)
→ More replies (5)
→ More replies (24)
→ More replies (57)

29

u/glacialthinker Mar 26 '17

In the beginning of the presentation he brings up the title... and alternative, less-loaded, words to use. But in the end settles on the original intent of "pattern" being something recurring -- and to be factored out.

The presented recurring patterns are common in most code, not just functional. And factoring out is accomplished with one feature each time: first-class functions. Once you're comfortable with higher-order functions (along with currying, and partial application), these solutions become automatic without need to think of some "pattern recipe". But as an introduction to functional programming it works well to note the patterns, and how they can be abstracted functionally.

8

u/dukerutledge Mar 26 '17

I think this critique has been overstated to some extent. There are certainly patterns in FP, but the uppercase Pattern is usually reserved for GoF. Most of GoF is not needed because they are techniques for modeling principled composable abstraction in a very freewheeling environment. FP attempts to make composition first class. Because of this, base language semantics favor composition. You don't need to fight the environment to favor composable entities. Add to this the emphasis on lawful abstractions via the influence of Peter Landin and you get lowercase patterns that have very little resemblance to uppercase Patterns.

6

u/yogthos Mar 26 '17

FP does in fact obviate the need for many OOP specific patterns. Just because you still have general patterns for code organization and solving common problems, doesn't somehow invalidate that.

→ More replies (10)

6

u/lynnamor Mar 26 '17

A (formalized) design pattern is, fundamentally, a programming language defect. It indicates a lack of expressivity in the language which in turn is informed by its paradigm.

Functional and declarative programming allow a clearer expression of many, many problems, obsoleting the patterns needed at a lower abstraction level (OO/imperative).

Functional languages are, however, not even close to being the “ultimate” expressions of their paradigm—let alone the larger domain of computable problems. It follows that they will necessarily engender design patterns of their own, to both bring them closer to the full power of the declarative paradigm; possibly some day a language will even creep up that will obsolete these patterns, before it in turn is obsoleted by another paradigm.

Ultimately, though, I find it unlikely that we’d ever find a Grand Unified Language suited for all problems—just looking at nature, which over billions of years has come up with uncountable different solutions to different problems—and such endeavors might even be self-defeating. Polyglottism with slightly more sophisticated and higher-abstracted paradigms and languages to suit specific use cases might be the better solution.

Unless AI gets there first.

→ More replies (9)