我见过的唯一反模式是过度单子化,由于单子非常有用,所以它介于不良实践和反模式之间。
假设你有一些P
你想对你的一些对象真实的属性。您可以使用 P monad 装饰您的对象(在 Scala 中,paste
在 REPL 中使用以使对象及其伴侣粘在一起):
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
}
好的,到目前为止一切顺利;我们通过制作value
aval
而不是将其制作为共单子(如果这是我们想要的)来作弊,但我们现在有一个方便的包装器,我们可以在其中包装任何东西。现在让我们假设我们也有属性Q
和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)
}
所以我们装饰我们的对象:
class Foo { override def toString = "foo" }
val bippy = R.pure( Q.pure( P.pure( new Foo ) ) )
现在我们突然面临一大堆问题。如果我们有一个需要 property 的方法,Q
我们如何得到它?
def bar(qf: Q[Foo]) = qf.value.toString + "bar"
嗯,显然bar(bippy)
是行不通的。有traverse
orswap
操作可以有效地翻转 monad,所以如果我们swap
以适当的方式定义,我们可以做类似的事情
bippy.map(_.swap).map(_.map(bar))
取回我们的字符串(实际上是 a R[P[String]]
)。但是我们现在已经承诺为我们调用的每个方法做类似的事情。
这通常是错误的做法。如果可能,您应该使用其他一些同样安全的抽象机制。例如,在 Scala 中,您还可以创建标记特征
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)
哇!容易多了。现在非常重要的是要指出并非一切都更容易。例如,使用这种方法,如果您开始操作Foo
,则必须自己跟踪所有装饰器,而不是让 monadic map
/flatMap
为您完成。但是很多时候你不需要做一堆实物操作,那么深度嵌套的 monad 就是一种反模式。
(注意:monadic 嵌套具有堆栈结构,而特征具有 set 结构;编译器没有内在的原因不能允许 set-like monad,但它不是典型的类型理论公式的自然构造。反模式是深堆栈难以使用这一事实的一个简单结果。如果您为 monad(或 Haskell 中的标准 Monad 转换器集)实现所有 Forth 堆栈操作,它们可能会更容易一些。)