Ответ 1
Единственный анти-шаблон, который я видел, - чрезмерная монадизация, и поскольку монады могут быть невероятно полезными, это падает где-то между плохой практикой и анти-шаблоном.
Предположим, что у вас есть свойство P
, которое вы хотите быть правдой для некоторых ваших объектов. Вы можете украсить ваши объекты с помощью монады P (здесь, в 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
a val
вместо того, чтобы сделать это comonad (если это то, что мы хотели), но теперь у нас есть удобная обертка, в которой мы можем что-то обернуть. Пусть теперь мы также имеем свойства 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 ) ) )
Теперь мы сталкиваемся с множеством проблем. Если у нас есть метод, который требует свойства Q
, как мы к нему подойдем?
def bar(qf: Q[Foo]) = qf.value.toString + "bar"
Ну, ясно, что bar(bippy)
не сработает. Существуют операции traverse
или swap
, которые эффективно переворачивают монады, поэтому мы могли бы, если бы мы определили swap
соответствующим образом, сделаем что-то вроде
bippy.map(_.swap).map(_.map(bar))
чтобы вернуть нашу строку (на самом деле, 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
, вам нужно будет следить за всеми декораторами самостоятельно, вместо того чтобы позволить монадическому map
/flatMap
сделать это за вас. Но очень часто вам не нужно делать кучу натуральных манипуляций, а затем глубоко вложенные монады - это анти-шаблон.
(Примечание: монадическое вложение имеет структуру стека, в то время как черты имеют заданную структуру, нет никакой причины, по которой компилятор не может допускать монтоподобные монады, но это не естественная конструкция для типичных формулировок теории типов. -pattern - это просто следствие того факта, что глубокие стеки сложны для работы. Они могут быть несколько проще, если вы реализуете все операции стека Forth для ваших монад (или стандартный набор трансформаторов Monad в Haskell).