Подтвердить что ты не робот

Существуют ли какие-либо документированные анти-шаблоны для функционального программирования?

В следующем месяце я собираюсь работать над новым проектом R & D, который будет использовать функциональный язык программирования (я проголосовал за Haskell, но прямо сейчас F # получил больше консенсуса). Теперь я играл с такими языками некоторое время и разработал с ними несколько инструментов командной строки, но это довольно большой проект, и я пытаюсь улучшить свои знания и технику функционального программирования. Я также много читал по этой теме, но я не могу найти ни книг, ни ресурсов, которые документируют анти-шаблоны в мире функционального программирования.

Теперь, узнав об анти-шаблонах, узнайте о неудачах умных людей: в ООП я знаю несколько из них, и я достаточно опытен, чтобы выбирать мудро, когда что-то, что обычно является анти- -паттерн, отлично соответствует моим потребностям. Но я могу выбрать это, потому что я знаю урок, полученный другими умными парнями.

Таким образом, мой вопрос: есть ли документированный анти-шаблоны в функциональном программировании? До сих пор все мои коллеги говорили мне, что они не знают, но они не могут сказать, почему.

  • Если да, укажите одну ссылку на авторитетный источник (каталог, эссе, книгу или эквивалент).
  • Если нет, пожалуйста, подтвердите свой ответ правильную теорему.

Пожалуйста, не включайте этот вопрос в список: это логический вопрос, который требует только доказательства для оценки ответа. Например, если вы Олег Киселев, "Да" достаточно, так как каждый сможет найти ваше эссе по этой теме. Однако, пожалуйста, будьте щедры.

Обратите внимание, что я ищу формальные анти-шаблоны, а не простые вредные привычки или плохие практики.

Из связанная статья Википедии о Anti-Patterns:

... должно присутствовать как минимум два ключевых элемента, чтобы формально отличить фактический анти-шаблон от простой дурной привычки, плохой практики или плохой идеи:

  • некоторые повторяющиеся шаблоны действий, процесса или структуры, которые изначально кажутся полезными, но в конечном итоге приводят к более плохим последствиям, чем положительные результаты, и
  • существует альтернативное решение, которое четко документировано, доказано на практике и повторяемо.

Более того, под документированным я имею в виду что-то из авторитетных авторов или известных источников.

К языкам, к которым я привык, относятся:

  • Haskell (где я действительно начинаю думать, что если код компилируется, он работает!)
  • Scala
  • F #

но я также могу адаптировать знания о анти-шаблонах, задокументированных на других функциональных языках.

Я много искал в Интернете, но все ресурсы, которые я нашел, либо связаны с ООП, либо работают с макетом (определяют переменную в начале функции и т.п.).

4b9b3361

Ответ 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).