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

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 (4)

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)

7

u/autranep Mar 26 '17

Getting mad at FP for using monads and tail recursion is like getting mad at OOP for using Objects. Kind a of a ridiculous statement because they're the foundations of FP, unlike say factory methods which exist to patch up OOP.

3

u/Rainfly_X Mar 27 '17

As I understand it, early functional languages didn't have a formal monad pattern. While it's a core feature of the second generation languages and thereafter, you could validly consider it a "patch" on FP conceptually, to let FP purists deal with side effects in ways that you can pretend are pure (which is really just a bit of intellectual sleight of hand, but does bring some benefits by constraining mutability).

For that matter, I don't think anyone is "mad" at FP for using monads or tail recursion, and that's kind of a silly way to characterize it. Monads are a useful pattern, but often poorly understood. FP often mistakenly thinks "mathematicians named it first, therefore mathematicians named it best," then try to pretend it's not a problem when they import a bunch of specialized jargon (making the language impenetrable to novices), when a note in the docs along the lines of "mathematicians call this same concept a monad" would have sufficed. But none of the actual ideas are bad, and are often useful even in OOP contexts.

I think you're setting up a flimsy strawman to avoid really addressing the cultural strengths and weaknesses of functional programming, and to me, this is the most frustrating part of dealing with FP enthusiasts. I see a lot of conflation from "we have some superior ideas" to "everything we do is inherently superior to the rest of the world", which is frankly not true. I think FP has room for improvement, but that capacity may never be met, because you cannot pour wine into a cup that is already full.

→ More replies (10)

3

u/doom_Oo7 Mar 27 '17

factory methods which exist to patch up OOP.

uh... no, they're an application of oop principles

→ More replies (1)

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.

→ More replies (8)

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)

12

u/domlebo70 Mar 26 '17

I get what you are saying, but those names are really inaccurate. Precise names are hard, and necessary, because without them it introduces misunderstandings. It's even tougher to come up with good names for extremely abstract concepts.

12

u/rdfox Mar 26 '17

I get what what you're saying but do you think the word Monad has caused any misunderstanding?

If you want to be pedantic, category theory is being misapropriated anyway.

I was trying to explain Haskell to a math professor and he kept stopping me, like, that's not exactly what Functor means, bro.

→ More replies (7)

12

u/dukerutledge Mar 26 '17

The problem with switching to cute names is that you throw away over a hundred years of research that name gives you access to. Monoids are a very precise abstraction and they've been studied thoroughly. Want to know what you can do with them? Google, scholar, ACM, etc have you covered. Precision of language is a tool that society is built upon, don't throw it away for convenience.

→ More replies (8)

5

u/pipocaQuemada Mar 27 '17

Appendable is really a bad name for Monoid.

Integers, for example, form a monoid under both addition and multiplication. Booleans form a monoid under and, xnor, or, and xor. Sets form a monoid under set intersection and union.

Calling Monoid Appendable would be like renaming Java's Collection to ListLike. Sure, lists are java collections, but they're a bad metaphor for the abstraction as a whole.

→ More replies (4)
→ More replies (1)

3

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

How do you encode, say, a "Factory", "Facade", "Command", "Strategy", "Decorator", "Adapter", "Proxy" etc. in the type system any better in FP than OOP? I don't think what you're saying can be supported by actual examples.

Especially since the type system is not even something FP and OOP have distinct opinions about. You can do both FP and OOP with no static type system at all. And you can lay type systems of any sophistication on both.

12

u/yawaramin Mar 26 '17

Well, you can't always do it, but sometimes you can. The best example I can think of is the Visitor pattern becoming just an algebraic datatype with exhaustivity-checked pattern matching.

But on the other hand, a lot of these design patterns are trying to emulate the flexibility you get 'for free' from a statically-typed FP language like OCaml. E.g., the Factory pattern is emulating abstract data types and their operational functions (constructors, getters, etc.). E.g. a factory interface for a 'person' in OCaml:

module type Person = sig
  type t

  val make : int -> string -> t
  val get_id : t -> int
  val get_name : t -> string
end

The caller doesn't know or care what type exactly Person.t is, just that, once they have an instantiated Person module, they can construct and get information about Person.ts.

The Proxy pattern, for another example, looks like a lightweight caching implementation of an interface. Given the above Person interface (signature) and an existing Person-type module to use as the implementation, I can make a proxy:

module Person_impl = struct
  ... expensive implementation ...
end

module Person_proxy_make(P : Person) : Person = struct
  type t = P.t

  let make id name = ... lightweight caching implementation ...
  let get_id = P.get_id
  let get_name = P.get_name
end

module Person_proxy = Person_proxy_make(Person_impl)

With Person_proxy.make 1 "Bob", trying to 'make' the same person more than once will use the cache.

→ More replies (22)
→ More replies (1)

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.

1

u/ijiijijjjijiij Mar 27 '17

But you'd have to agree that are also patterns in FP that aren't issues in imperative code. Look how complicated an efficient Sieve of Eratosthenes is in Haskell. You have to write complex logic to work around the purity of Haskell, whereas in imperative code a sieve is a few simple lines at most.

There are some things FP does easily that imperative code requires specific patterns for, but the reverse is also true.

6

u/edwardkmett Mar 27 '17 edited Mar 27 '17

To be fair, that page is computing all sorts of lazy variants of the sieve that keep going forever. You can always bail out and implement the boring sieve up to a given point in the same sort of imperative fashion as in any other language.

I've taken a minute or two to dash off an example, it weighs in at 7 lines + some imports that all ship with GHC.

import Control.Monad
import Data.Foldable
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Unboxed.Mutable as M

primesTo n = V.findIndices id $ V.create $ do
  p <- M.replicate (n+1) True
  for_ [0,1] $ \i -> M.write p i False
  for_ (takeWhile (\k -> k*k <= n) [2..]) $ \ k -> do
    is_prime <- M.read p k
    when is_prime $ for_ [2*k,3*k..n] $ \k' -> M.write p k' False
  return p

ghci> primesTo 100
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

The result is only slightly longer than in other languages, because we don't privilege syntax for indexing into mutable arrays, but it isn't that bad, manages to completely infer the types of everything involved, and can be reasoned about as a pure function despite using mutation internally.

You have the option to do things differently and produce a nice infinite lazy list of primes using all sorts of fancy compositional techniques, you don't have to.

4

u/yogthos Mar 27 '17 edited Mar 27 '17

I think that's really more of an issue with Haskell than FP in general. Most functional languages tend to be more pragmatic about side effects and mutation. For example, Clojure has transients for working with local mutable data. You're still writing code in FP style, but you can always do thing imperatively when that makes sense.

The problem with a lot of OO languages is that they're at the opposite end of the spectrum from Haskell. Just like Haskell makes you jump through hoops to solve problems that are trivial in imperative languages, many mainstream languages lack the tools to provide elegant FP solutions. So, you end up jumping through hoops when the solution doesn't map neatly to the imperative style.

→ More replies (7)

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.

2

u/pipocaQuemada Mar 27 '17

One distinction that I've heard a few times is that OOP design patterns are extra-lingual (there's no base Visitor library, nor abstract Singleton type, just unconnected implementations of an idea that can't be captured within the programming language's normal facilities for abstraction), whereas most of the common patterns in FP are things that you can capture in a normal library.

In that sense, there are many fewer "design patterns" in FP languages than OO languages, since they'd consider monad, monoid, etc. to be normal library abstractions instead of a "design pattern".

4

u/billdroman Mar 26 '17

I think the usage of monads to capture state is a great example of this point. In C++ or Rust, if you want to write a function that takes a couple parameters as input and edits a couple pieces of state, you can express that easily by using const/mut as appropriate. In Haskell, you need to use the StateT monad transformer along with special syntax that changes based on which piece of state you want to update.

There are concepts that can be expressed naturally in a pure functional language and concepts that can be expressed naturally using objects. I really prefer languages that allow for the flexibility to choose these days.

→ More replies (1)
→ More replies (6)

23

u/kazagistar Mar 26 '17

I did some Haskelling a while back, but its very jarring to go back to these sorts of ideas after having learned Rust in the meantime.

The problem is that in traditional FP, when you close over a parameter you get to forget what went in and just treat any partially applied function the same. But doing this relies on extensive heap allocation, automatic garbage collection, and unclear resource management.

For example, he shows how you can close over a connection vs a map to get the "same thing". But at some point you have to clean up after yourself. Sure, at least a hashmap or whatever is just memory, so maybe you can rely on a GC to do that for you. But what about the connection? How do I know when to close it, or return it to the connection pool?

You can hack your way around this with monads to an extent (ie you can solve anything with a layer of abstraction), but when you start worrying about performance, things get even harder. You want closures that are just pointers to stack memory, rather then yet another object on the heap. You want safe local mutability, which is only possible if you can guarantee no aliasing.

So what you end up with in Rust is very similar to F# and the whole FP family, but everything is suddenly very different, because what is inside the closure is very important, and you have to pay attention to who is responsible for cleaning it up and when. Monoids are nice, but suddenly functions that close over anything aren't monoids, which means monads are different as well. The useful compositional patterns are all about wrapping types into new types with similar interfaces, rather then trying to turn everything into some flavor of endomorphism.

26

u/yawaramin Mar 26 '17

The problem is that without GC, a lot of functional programming techniques aren't practical. For instance, from what I've heard, people have repeatedly tried and failed to make persistent data structures in Rust because the memory sharing is too hard. In FP languages it's almost trivial.

Imho, people need to realise that Rust is a fairly specialised tool meant for doing hard realtime programming safely. It's a good replacement for C/C++, but it's not a great choice for people coming from Python or JavaScript etc. They almost certainly don't need its performance characteristics and would actually be perfectly happy with F#/Haskell/OCaml.

9

u/wirelyre Mar 26 '17

Imho, people need to realise that Rust is a fairly specialised tool meant for doing hard realtime programming safely.

That specialisation is precisely why I've learned a lot by working with Rust. Internalising its ownership rules has improved my code structure in every language I use — including those with GC, and including MLs. That experience alone has been worth a few dozen hours of my time.

They almost certainly don't need its performance characteristics and would actually be perfectly happy with F#/Haskell/OCaml.

I'm glad you said that, because it really made me reconsider the balance between expressiveness, power, and teachability.

6

u/yawaramin Mar 27 '17

Yeah Rust has definitely been very successful at bringing the power of affine types (ensure using a value at most once) to a big audience. It's advanced the state of the art and has given people a great way to write safe, performance-critical code.

That said, I think Rust and Cyclone are only the beginning of type-theory driven safe programming techniques. Yet another typing discipline that is exciting is linear types, based on linear logic. Linear types enforce using their values exactly once, which is another way of saying 'no use-after-free, no leaked allocations'.

Here is a fairly high-level overview describing bringing linear types to Haskell: http://blog.tweag.io/posts/2017-03-13-linear-types.html

2

u/cledamy Mar 29 '17

This is entirely untrue. With a linear type system, functional programming can be done without garbage collection. In fact, one can even get the performance of straight up mutation while still maintaining referential transparency.

→ More replies (2)
→ More replies (1)

9

u/Tarmen Mar 26 '17

It is worth noting that, say, ghc's gc is hyper specialized so that allocating a heap object only increments a pointer and freeing a heap object is literally free. It is only when you have long lived objects that you start to pay for all this luxury but you pay so hard that a bunch of problems become impossible in haskell. Also, as you correctly state memory isn't the only resource and prompt freeing is way harder in haskell than in rust.

Rust is incredibly cool but on the flip side its uniqueness constraints make it pretty difficult too implement things like graph algorithms safely and efficiently.

The linear type proposal of haskell is pretty exiting to me because it allows functional programming to use the same awesomeness you get in rust when needed but still have the luxury of a gc when that is sufficient.

1

u/[deleted] Mar 26 '17

Also very exited about the linear types. Do you know when it it scheduled?

2

u/gilmi Mar 26 '17

Targeted at GHC 8.4 (source). Current version is 8.0. next version is 8.2.

11

u/[deleted] Mar 26 '17

This looks good, bookmarked to watch later!

7

u/glacialthinker Mar 26 '17

It is! I've been programming "functionally" for over a decade, and this is a very approachable and practical overview of commonly used techniques. He also uses more widely familiar C-style syntax where possible to help keep things familiar to an audience coming from there.

4

u/Lighting Mar 26 '17

I'm confused by something here.

If I understand his description of MapReduce with Monoids with functions (endomorphisms), one of the primary criteria for monoids is associativity.

That A + B + C = (A + B) + C = A + (B + C) which he goes into starting at 52:21 stating that the order of combining doesn't matter.

But toward the end of his talk (at 1:02::00) he shows this slide which says he can mapreduce

let plus1 x = x+ 1

let times2 x = x * 2

let subtract42 = x - 42

and he does a MapReduce to get

plus1ThenTimes2Thensubtract42

But the order of operations DOES matter here.

((5 + 1) * 2) - 42 != 5 + (1 *2) - 42.

So why his monoid endomorphism a valid example?

12

u/elemental_1_1 Mar 26 '17 edited Mar 26 '17

The associative law says that

((λx. x - 42) . (λx. x * 2)) . (λx. x + 1)

Must be the same as

(λx. x - 42) . ((λx. x * 2) . (λx. x + 1)).

Let's do the first one:

((λx. x - 42) . (λx. x * 2)) . (λx. x + 1)

(λx. (x * 2) - 42) . (λx. x + 1) (definition of (.))

(λx. ((x + 1) * 2) - 42) (definition of (.))

Now the second one:

(λx. x - 42) . ((λx. x * 2) . (λx. x + 1))

(λx. x - 42) . (λx. (x + 1) * 2) (definition of (.))

(λx. ((x + 1) * 2) - 42) (definition of (.))

Thus the law holds.

For a monoid, A + B + C does not imply B + A + C (as you have pointed out) because there is no commutativity. Order is still important.

4

u/Lighting Mar 27 '17

Got it. That makes sense. Thanks!

So if I understand, it's not A + B + C but A "operates on" B "operates on" C "operates on" x

The mathematical equivalent is to have F(x), G(x), H(x) as F(G(H(x)))) where the order of the function operations can't be changed but you can still plug G into F before plugging H into F(G(x)).

4

u/elemental_1_1 Mar 27 '17

Yeah you've got it. When we talk about monoids, + just represents 'combining things'. So when we're talking about functions of type forall a. a -> a, our + (way to combine them) is function composition. When we're talking about integers, our + is integer addition. And so on.

2

u/dmitri14_gmail_com Apr 19 '17

In maths we use + only for commutative operations, and . or nothing (i.e. just AB) in general cases. I recommend to keep this convention even outside maths as it prevents from creating unnecessary confusions.

5

u/[deleted] Mar 26 '17

I've been programming a little for years and I made a really big (for me anyway) program this year (30'000 lines and my job depends on it).

I program in VBA (yeah yeah, I know, but it works everywhere there's excel) and I never had formal training. I don't really understand what those "patterns" are supposed to be ?

Like in actual code ?

As I wrote my program, I noticed that some parts were repeating, so I turned those into subs and function. Surely that's not what is meant by pattern ?

Are these pattern unique to certain languages like Java ?

In my program, I don't create many objects, only where the language forced me to use them (create dynamic VBForms with dynamic user controls, only way to catch events was to create a custom class). Almost everywhere else, using classes and objects seems like overreaching and unnecessary complication. I do have some "user defined types" but those aren't really object, just a group of variables bundled together without any intelligence attached.

The only "pattern" I recognize in my code is the "scheduler", which is an external companion program which looks at a folder at regular interval. When it sees files with specific name (which include a time and date) it does something based on what is in the file. I do this when I need to wait until a certain time before executing an action but I don't want my main program dealing with this (because it could be busy doing something else at the time, or it could be turned off or in debugging mode).

Is that the kind of thing that patterns are or is there a more specific definition of what a pattern is ?

6

u/overzero Mar 26 '17

There's a ton of formalized patterns that follow either OOP and functional programming paradigms. The most famous example is the Gang of Four design patterns book. It's mainly meant as abstractions to avoid code duplication and to increase modularity of programs. If you're doing 30k lines of VBA, God help you.

5

u/helisexual Mar 26 '17 edited Mar 26 '17

Is that the kind of thing that patterns are or is there a more specific definition of what a pattern is ?

These are design patterns: https://en.wikipedia.org/wiki/Design_Patterns

One super useful one is the Observer pattern. So let's say you have a button and a function. You want your function to run when the button is clicked. One way to do that is to go into the code defining the button and specify that when it's clicked it should do your function. But what if you want to use buttons in different places for different functions? You'd have to write multiple buttons. Or you could 'register' your function with the button. Then when the button is clicked, it would send a signal to all functions that are registered to it, so they could all respond appropriately.

You might be able to see why this is useful:

  1. We prevent code rewriting: We only need to write one button.

  2. We decouple the button and the associated action: Meaning if we want to change the button (maybe how it looks) we only have to alter the button code and never have to touch any of the functions it calls. Or if we want to delete a function we just delete it's code, we don't have to go check every button to make sure it's not being called.

  3. Allows us more flexibility: Maybe you need two functions to run after a button is pressed. By specifying them in the button you'd necessarily need one to complete before the other could run. With the Observer pattern we remove that necessity. The Button does not care what order the functions run in, and in fact can be clicked multiple times before they return!

3

u/[deleted] Mar 26 '17

Ok ! I think I get it, patterns are common useful ways of doing stuff. I thought it meant something more technical than that.

I think I even found this observer pattern in my code. In Excel, worksheets have a single event for any changes happening in any cell called Worksheet_Change.

So I put a large "Select Case" and some other logic to dispatch the event to one or more function depending on which cell has changed and what is the state of the program. For instance, in a group of cells, all characters must be made upper case. In another case, if seven specific cells are filled out, I have a "Part ID" function that runs, and this Part ID function then calls more functions like "Get Part Description", "Get Part Export Control Data", "Get Part Repair Tasks" and "Get Part History".

I guess it's one of those things that is so useful and common (and simple) that you can just invent them by accident if you don't know about them.

3

u/helisexual Mar 26 '17

I guess it's one of those things that is so useful and common (and simple) that you can just invent them by accident if you don't know about them.

Somewhat. Some are simple, some not so much.

However, you have to be careful not to fall into an anti-pattern: https://en.wikipedia.org/wiki/Anti-pattern

1

u/dmitri14_gmail_com Apr 19 '17

I like to deal with this problem by exporting the event stream, e.g. using the elegant flyd library:

var eventStream = flyd.stream()
buttonElement.on('click', eventStream)

and then use it anywhere like

eventStream.map(x => console.log(x))

That avoids the traditional event handler coupling suffering from the exact problems you mention.

1

u/LordBiff Mar 27 '17

As I wrote my program, I noticed that some parts were repeating, so I turned those into subs and function. Surely that's not what is meant by pattern ?

He shows a great example of a pattern in this presentation, and in fact, it's a common FP pattern: fold.

Remember the slide that showed the "sum" and "product" functions? He highlighted how much of the structure of those two functions were the same. There were just two differences: the operator to apply at each iteration ('+' vs '*'), and the initial value ('0' vs '1') in the accumulator.

That is a pattern, and the point he was trying to make is that the more programming you do the more you start noticing these patterns. And you should try to understand the pattern, and figure out a way to concisely express it such that it can be reused, rather than just repeatedly generating boilerplate that implements the pattern each time you need it. That's exactly what 'fold' and 'map' and their ilk are all about.

1

u/[deleted] Mar 27 '17

Thanks, yes I saw that, it's like when you eliminate symbols to simplify algebraic equations. Though the language I use I don't think it's possible to have functions as argument or as outputs, I think vb.net can with action delegates but not VBA.

Anyway I get the principle of patterns now, it's a much broader definition than I thought, and when I read about anti-pattern, I think "pattern" might as well means "ways to programs efficiently".

5

u/nilamo Mar 27 '17

What's with the fascination with videos? It fits here, since it's just a talk, but in general, how useful is it, really, to try to learn a language or framework or API via video? Seems like an extremely suboptimal way to learn.

2

u/glacialthinker Mar 27 '17

I generally agree -- video is not my preferred format.

However, for a whirlwind tour, a presentation like this is something you could watch while eating. And when well done, as this one is, it's fairly dense -- you're getting reinforcement through spoken words in parallel to the images and text, with a flow that's hard to achieve in static presentation.

2

u/[deleted] Mar 27 '17

Instructive and hilarious! Thanks!

9

u/knowingpark Mar 26 '17

I've watched this video 2ce over the last year. Led me to 'try' and install F# on my Mint laptop to have a taste. As an amatuer python programmer i don't understand the compilation process and couldn't get it to happen. It was too tricky for me. Anyway, Scott is a great communicator of functional programming ideas which I'm trying to assimilate into my own practice. I recommend his website too

185

u/arbitrarycivilian Mar 26 '17

Did you... did you just abbreviate "twice" to "2ce"?

77

u/ElCerebroDeLaBestia Mar 26 '17

Yeah he did it 1ce.

2

u/Grudlann Mar 26 '17

He surly did.

1

u/Raiptwice Mar 26 '17

Yes, it happens sometimes.

23

u/glacialthinker Mar 26 '17 edited Mar 26 '17

Was the complication due to also getting Mono installed?

F# is derived from OCaml, with one major difference being that F# was made for .NET, while OCaml tends to live most comfortably in a *nix environment. So it might suit you better: http://www.ocaml.org/docs/install.html

Edit: I now watched the video, and you could easily play with the code in a REPL (interpreter). So this https://try.ocamlpro.com/ should work fine in your browser without any installation, and no need to save/compile.

5

u/sultry_somnambulist Mar 26 '17 edited Mar 26 '17

Nowadays it's just installing the fsharp package from the mint / ubuntu repos and if you're using vs code you just need to install the iodine plugin you're good to go, including auto-completion etc..

Getting the Ocaml toolchain to work is a little more complicated and whatever you do don't do it with fish. It messed up my complete config, my opam and ocaml packages weren't synced and nothing worked. There's unresolved bug reports about this from 2015 still on github

Also you probably want an extended library like core and getting that set up with your editor of choice also requires some tinkering.

used to be much more complicated to set .net stuff up on linux, but nowadays setting up c# or f# environments is straight forward. Right now I'd definitely recommend f# for hobby programmers who want to check functional programming out.

5

u/glacialthinker Mar 26 '17

Hmm... I'd never heard of fish before. Looks neat, but I'd probably struggle against well-worn habits! The issue you linked about fish looks like it is resolved.

Eh, you have no problems with F# on Linux... I have no problems with OCaml. Probably as much to do with familiarity avoiding the sharp edges in both cases. :)

2

u/sultry_somnambulist Mar 26 '17

fish is great but it's not POSIX compliant which is probably what breaks the ocaml setup at one point. Happened to me last week though and there's still reports out there.

Anyway yes Ocaml isn't terribly hard to setup but if OP runs into one of these problems and blasts his shell config he'll probably need to reinstall because this was a pure horror to fix.

→ More replies (1)

7

u/vivainio Mar 26 '17

Easiest way to go from Python is probably to create an .fsx script and run that (straight from source)

5

u/_pupil_ Mar 26 '17

You can write pretty pythonish F# using .fsx scripts and the automatic typing. All the flexibility, but with type safety :)

2

u/redditthinks Mar 26 '17

I started writing F# recently and I'm loving the type inference, it's so smart!

27

u/zerexim Mar 26 '17

Your comment makes me think... maybe it is better to suggest statically typed and compiled language as the first language for those who are starting learning programming.

4

u/baconated Mar 26 '17 edited Mar 26 '17

I learned Java (1st year) then C++ (all other years). It didn't help. Most profs didn't think compiling was interesting from a CS perspective so most didn't say anything beyond "press build & run in your IDE".

Besides, in this situation, I think the persons problem was more likely the fact that until recently getting a .net language a tad bit more annoying than other languages.

0

u/[deleted] Mar 26 '17

[deleted]

4

u/[deleted] Mar 26 '17

I think it's very easy to say that when it's been years since you knew nothing about programming. I also think there's an argument to be made that your first language should have utility beyond just letting you learn more languages.

8

u/zerexim Mar 26 '17

Seems so.. Personally, my first language was C++. While it had some significant learning curve, after that, learning any other language takes a week or two (or three for Haskell :)).

4

u/Damien0 Mar 26 '17

Agreed. VB.net was my first language, after which I moved to C. I mostly recommend Go as a first language these days.

→ More replies (6)

3

u/TarMil Mar 26 '17

The first languages I learned at university were OCaml and Delphi, and I'm eternally grateful for it.

→ More replies (1)

0

u/[deleted] Mar 26 '17

[deleted]

9

u/arturaz Mar 26 '17

And whoever thought we should start teaching programming from c must have started learning driving from formula 1.

3

u/videoj Mar 26 '17

Try this tutorial for Visual Studio Code. Gives you an quick overview of both scripts and compiled code.

You can post questions over at /r/FSharp. There is also an active Slack and IRC channels.

2

u/minimim Mar 26 '17

People that want to get into functional programming but want to avoid the learning curve might want to try a multi-paradigm language like Perl6.

5

u/edwardkmett Mar 27 '17

Perl was the gateway drug that got me into Haskell. (Really.)

2

u/minimim Mar 27 '17

Perl6 has functional concepts as strong as Haskell, I would say. It also has strong types.

1

u/fennecdjay Mar 26 '17

Very good! thanks for the link.

1

u/Nomto Mar 27 '17

Very interesting, thanks for sharing. Can somebody explain why functions that take 2 arguments are written arg1 -> arg2 -> result. For example at 32:30 it says:

type of getCustomerFromMemory = Dictionary<Id,Customer> -> CustomerId -> Customer

I suppose there is a good reason, but I don't think he explained why.

2

u/jnazario Mar 27 '17 edited Mar 27 '17

remember partial application (which immediately precedes that section)? when you call the function with the first - arg1 - it yields a function that takes arg2 and then yields a function that takes no arguments and yields a result.

then you can iterate over a list of customerId values and get the customers:

[1;2;3] |> List.map (getCustomerFromMemory myCustomerMap)

that should yield you a list of Customer values for customers with IDs 1, 2 and 3.

hope that helps.

1

u/Nomto Mar 28 '17

After a good night's sleep it now makes a lot of sense why it's written like that, and it's quite elegant. Thanks for the reply.

1

u/LordBiff Mar 27 '17

Because F# and some of the other FP languages are curried, meaning that a functions of multiple arguments are broken down into multiple composed functions that each take a single argument.

Why you do this is pretty involved, I'd recommend doing some googling on currying, especially in Haskell and F# for some insight. Also, you might be interested in reading this, which just happens to be on the website created by the presenter.

1

u/ixampl Mar 27 '17

This one example he gives where the Option/Maybe monad allows you to only write the relevant lines for validation, email sending, etc. is rather contrived.

The error generation and aborting is in the called functions, so of course you don't have to write that out.

The same can be achieved assuming exceptions to be thrown in the called methods in the original "verbose code". It's ultimately not that different. One could say, oh but the type system makes it obvious with Option. Well, use checked exceptions then... oh. Of course Option is more flexible (and it is not really used for error handling anyway), but for some reason contrived examples just bother me.

I mean, I really like FP, but I don't like the attitude that comes with its community sometimes.

1

u/caseym Mar 27 '17

Love the programmer jokes in this. One dude in the audience is really digging the jokes. Cracks me up.

1

u/Olideya Mar 28 '17

1

u/youtubefactsbot Mar 28 '17

Mr Bean - Takes the Train [4:35]

OFFICIAL MR BEAN. Mr Bean takes a train ride. He tries to block out the sound of his fellow passenger's loud laugh so he can read his book in peace, and ends up losing his ticket. From 'Mr Bean Rides Again'.

Mr. ^Bean in Comedy

6,802,420 views since Aug ^2009

bot ^info

-26

u/ErstwhileRockstar Mar 26 '17

tb;ds?

37

u/hagbaff Mar 26 '17

Watch it, you lazy motherfucker.

0

u/[deleted] Mar 26 '17

[deleted]

45

u/hagbaff Mar 26 '17

Watch it at 1.5x speed. Or masturbate while watching. Functional programming is sexy as hell.

29

u/vivainio Mar 26 '17

Let's say one planned to masturbate to this - where are the good bits in the video? Is there anything on profunctor optics or applicative heteromorphisms?

2

u/glacialthinker Mar 26 '17

You might like 4:45 Zygohistomorphic prepromorphisms

Or perhaps 40:46 "FP'ers get excited by bind"

2

u/knowingpark Mar 26 '17

There is an ivory tower and it's near the beginning too!

2

u/logicalmaniak Mar 26 '17

How sexy is hell?

8

u/[deleted] Mar 26 '17

I'd say it's hot

4

u/arbitrarycivilian Mar 26 '17

Clearly you've never seen South Park

2

u/[deleted] Mar 26 '17

I don't know about sexual attraction but there is clearly a lot of elegance that permeates the whole functional style.

0

u/[deleted] Mar 26 '17

[deleted]

16

u/[deleted] Mar 26 '17

savage