Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there any documented anti-patterns for functional programming? [closed]

People also ask

How many anti-patterns are there?

The six anti-patterns I will discuss in this article are Spaghetti Code, Golden Hammer, Boat Anchor, Dead Code, Proliferation of Code and the God Object.

What of the following design patterns are treated as anti-pattern?

Anti-patterns are certain patterns in software development that are considered bad programming practices. As opposed to design patterns which are common approaches to common problems which have been formalized and are generally considered a good development practice, anti-patterns are the opposite and are undesirable.

Which anti-pattern is most common among new programmers?

1. Spaghetti Code. This is one of the most common types of anti-pattern you will find in software development.

What is the difference between a pattern and an anti-pattern?

An anti-pattern is the evil twin of a good design pattern. A good design pattern is a reusable requirement, design, or implementation that solves hard problems in a standard way, which is well designed, well documented, easy to maintain, and easy to extend.


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).)