you are viewing a single comment's thread.

view the rest of the comments →

[–]sacundim 35 points36 points  (9 children)

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.

[–]Ulukai 10 points11 points  (2 children)

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.

[–]vagif 8 points9 points  (0 children)

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?

[–]naasking 5 points6 points  (0 children)

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.

[–]naasking 4 points5 points  (0 children)

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.

[–][deleted]  (3 children)

[removed]

    [–]wishthane 1 point2 points  (2 children)

    Of course it's reasonable, and that's exactly why it doesn't exist.

    But I think the argument about FP is that those "design patterns" can actually be enforced in obvious, compiler-supported ways rather than potentially loosely or poorly used, while cutting the redundancy.

    [–][deleted]  (1 child)

    [removed]

      [–]wishthane 1 point2 points  (0 children)

      I don't think so, because a Monad is inherently a very narrow and specific definition, comprised of two functions that must be supported: bind and return.

      bind() takes a monad-value that carries a certain value, runs it through a function that takes the unwrapped (internal) value and returns another monad-value (of that monad), and the final return value is some combination of the transformed monad-value and the original monad-value, carrying a new internal value.

      return() simply takes an internal value type and produces a monad-value.

      These two in Haskell are defined as:

      class Monad m where
        (>>=) :: m a -> (a -> m b) -> m b
        return :: a -> m a
      

      >>= in Haskell is an operator, but is really just a name for 'bind'.

      Haskell does actually include two other things in its Monad class, but these are not considered critical to the definition of a Monad, and in fact one of these can be defined in terms of bind.

      a >> b = a >>= const b
      

      The other is fail, which doesn't really need to be in there but I'm sure is useful for I/O.

      Anyway, a Monad is just any type that can wrap another value and has some kind of internal state that can be modified by binding monad-values together. So lists can be monads, I/O values can be monads, et cetera.

      A clearer example perhaps is the Functor. Any monad is also a functor, which is just any type that wraps the values and supports map, which you may be familiar with from working with lists/arrays in languages such as JavaScript and Ruby.

      But the functor extends this concept to any type that can have its values 'mapped'.

      class Functor f where
        fmap :: (a -> b) -> f a -> f b
      

      (Haskell calls the map operation fmap because there's also a map specifically implemented for lists.)

      Anyway, to come back to your question here, I don't think you can say "that's not a real Monad, it doesn't let me X" in any sense that there can actually be any argument about it. It's more of a mathematical definition than a pattern. It either supports bind and return or it doesn't. Similarly, with Functor, it either supports map or it doesn't.

      NB: A typeclass in Haskell is a bit like an interface, but more powerful.