28

Next month I'm going to work on a new R&D project that will adopt a functional programming language (I voted for Haskell, but right now F# got more consensus). Now, I've played with such languages for a while and developed a few command line tools with them, but this is a quite bigger project and I'm trying to improve my functional programming knowledge and technique. I've also read a lot on the topic, but I can't find any books or resources that document anti-patterns in the functional programming world.

Now, learning about anti-patterns means learning about other smart people failures: in OOP I know a few of them, and I'm experienced enough to choose wisely when something that generally is an anti-pattern, perfectly fit my needs. But I can choose this because I know the lesson learned by other smart guys.

Thus, my question is: are there any documented anti-patterns in functional programming? Till now, all of my collegues told me that they do not know any, but they can't state why.

  • If yes, please include one single link to an authoritative source (a catalogue, an essay, a book or equivalent).
  • If no, please support your answer by a proper theorem.

Please don't turn this question in a list: it is a boolean question that just requires a proof to evaluate the answer. For example, if you are Oleg Kiselyov, "Yes" is enough, since everybody will be able to find your essay on the topic. Still, please be generous.

Note that I am looking for formal anti-patterns, not simple bad habits or bad practices.

From the linked wikipedia article on Anti-Patterns:

... there must be at least two key elements present to formally distinguish an actual anti-pattern from a simple bad habit, bad practice, or bad idea:

  1. some repeated pattern of action, process or structure that initially appears to be beneficial, but ultimately produces more bad consequences than beneficial results, and
  2. an alternative solution exists that is clearly documented, proven in actual practice and repeatable.

Moreover by "documented" I mean something from authoritative authors or well known sources.

The languages that I'm used to are:

  • Haskell (where I'm really starting to think that if code compiles, it works!)
  • Scala
  • F#

but I can also adapt knowledge about anti-patterns documented in other functional languages.

I searched a lot in the web, but all the resources I've found are either related to OOP or to function layout (define variable at the beginning of the function, and the like...).

Community
  • 1
  • 1
Giacomo Tesio
  • 7,144
  • 3
  • 31
  • 48
  • Many of the articles in your link http://c2.com/cgi/wiki?AntiPattern are simply bad practices, and not paradigm-specific either. – AndrewC Apr 06 '13 at 09:49
  • @AndrewC good objection, let's change the link to wikipedia – Giacomo Tesio Apr 06 '13 at 09:50
  • I knew that article, but it's quite controversial as an answer to **this** question: 1) existential types are an Haskell **extension** that mimic OOP's classical abstractions, thus not strictly **functional** programming; 2) Palmer is expressing his own taste on the matter, since neither vty-ui, GlomeTrace, or XMonad suffer of design flaws that urge any refactoring; 3) as [the author stated](http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/#comment-2118), there was no concrete difference between the functional an oop way: anti-patterns actually cripple code! – Giacomo Tesio Apr 06 '13 at 10:41
  • 2
    Here is one: http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ – danidiaz Apr 06 '13 at 14:01
  • 4
    @NikitaVolkov What is right is not always popular, and what is popular is not always right. Also, Please take all discussion about this question to [this meta discussion](http://meta.stackexchange.com/questions/175507/how-can-i-turn-a-not-constructive-question-in-a-constructive-one/) – George Stocker Apr 08 '13 at 15:18

1 Answers1

14

The only anti-pattern I've seen is over-monadization, and since monads can be incredibly useful this falls somewhere in between a bad practice and an anti-pattern.

Suppose you have some property P that you want to be true of some of your objects. You could decorate your objects with a P monad (here in Scala, use paste in the REPL to get the object and its companion to stick together):

class P[A](val value: A) {
  def flatMap[B](f: A => P[B]): P[B] = f(value)       // AKA bind, >>=
  def map[B](f: A => B) = flatMap(f andThen P.pure)   // (to keep `for` happy)
}
object P {
  def pure[A](a: A) = new P(a)                        // AKA unit, return
}

Okay, so far so good; we cheated a little bit by making value a val rather than making this a comonad (if that's what we wanted), but we now have a handy wrapper in which we can wrap anything. Now let's suppose we also have properties Q and R.

class Q[A](val value: A) {
  def flatMap[B](f: A => Q[B]): Q[B] = f(value)
  def map[B](f: A => B) = flatMap(f andThen Q.pure)
}
object Q {
  def pure[A](a: A) = new Q(a)
}
class R[A](val value: A) {
  def flatMap[B](f: A => R[B]): R[B] = f(value)    
  def map[B](f: A => B) = flatMap(f andThen R.pure)
}
object R {
  def pure[A](a: A) = new R(a) 
}

So we decorate our object:

class Foo { override def toString = "foo" }
val bippy = R.pure( Q.pure( P.pure( new Foo ) ) )

Now we are suddenly faced with a host of problems. If we have a method that requires property Q, how do we get to it?

def bar(qf: Q[Foo]) = qf.value.toString + "bar"

Well, clearly bar(bippy) isn't going to work. There are traverse or swap operations that effectively flip monads, so we could, if we'd defined swap in an appropriate way, do something like

bippy.map(_.swap).map(_.map(bar))

to get our string back (actually, a R[P[String]]). But we've now committed ourselves to doing something like this for every method that we call.

This is usually the wrong thing to do. When possible, you should use some other abstraction mechanism that is equally safe. For instance, in Scala you could also create marker traits

trait X
trait Y
trait Z
val tweel = new Foo with X with Y with Z
def baz(yf: Foo with Y) = yf.toString + "baz"
baz(tweel)

Whew! So much easier. Now it is very important to point out that not everything is easier. For example, with this method if you start manipulating Foo you will have to keep track of all the decorators yourself instead of letting the monadic map/flatMap do it for you. But very often you don't need to do a bunch of in-kind manipulations, and then the deeply nested monads are an anti-pattern.

(Note: monadic nesting has a stack structure while traits have a set structure; there is no inherent reason why a compiler could not allow set-like monads, but it's not a natural construct for typical formulations of type theory. The anti-pattern is a simple consequence of the fact that deep stacks are tricky to work with. They can be somewhat easier if you implement all the Forth stack operations for your monads (or the standard set of Monad transformers in Haskell).)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 3
    @GiacomoTesio - I was hoping for other answers also. – Rex Kerr Apr 06 '13 at 22:37
  • 9
    Monads are used to encapsulate some aspect (eg. List encapsulates non-deterministic number of elements, Option encapsulates optionality, Future encapsulates asynchrony) and then remove it as a concern from the user. Scala doesn't make programming with monads as elegant as say Haskell does, but they may still be a significant benefit despite the cost. This example poorly and repetitively reproduces the ID monad for no value whatsoever, and simply says, don't add cost for no benefit. IOW, it is a straw-man. – Jed Wesley-Smith Apr 07 '13 at 23:50
  • @JedWesley-Smith - Er, isn't this the _definition_ of an anti-pattern? That the benefits are not worth the costs? By your definition, what _would_ be an anti-pattern (in any language) that is not a "straw man"? In any case, it _does_ enforce type-safety for these properties, so it is not "no value". – Rex Kerr Apr 08 '13 at 22:47
  • It would be nicer if you demonstrated the monad example in Haskell, where they are actually used and have syntax built into the language. – alternative Apr 23 '13 at 20:01
  • @alternative - I didn't want to get into comonads and monad transformers, and the accessor is much more natural in Scala (which sidesteps those). Also, I know Scala better, and as the question answerer I figured it was okay to make the conceptual point (which does not really change) in the language I'm more comfortable with. – Rex Kerr Apr 23 '13 at 20:15