29

来月は、関数型プログラミング言語を採用する新しい R&D プロジェクトに取り組む予定です (私は Haskell に投票しましたが、現在は F# の方が多くのコンセンサスを得ています)。現在、私はそのような言語でしばらく遊んで、それらを使用していくつかのコマンド ライン ツールを開発しましたが、これはかなり大きなプロジェクトであり、関数型プログラミングの知識と技術を向上させようとしています。私もこのトピックについてたくさん読んだことがありますが、関数型プログラミングの世界でアンチパターンを文書化した本やリソースを見つけることができません。

さて、アンチパターンについて学ぶことは、他の賢い人々の失敗について学ぶことを意味します.OOPでは、私はそれらのいくつかを知っており、一般的にアンチパターンである何かが私のニーズに完全に適合する場合、賢明に選択するのに十分な経験があります. しかし、他の賢い人たちから学んだ教訓を知っているので、私はこれを選ぶことができます.

したがって、私の質問は次のとおりです。関数型プログラミングに文書化された アンチパターンはありますか? 今まで、私の同僚は皆、何も知らないと言っていましたが、その理由を述べることはできませんでした.

  • はいの場合、信頼できる情報源 (カタログ、エッセイ、書籍、または同等のもの) へのリンクを 1 つ含めてください。
  • いいえの場合は、適切な定理によってあなたの答えをサポートしてください。

この質問をリストに入れないでください。これはブール値の質問であり、答えを評価するために証明が必要なだけです。たとえば、あなたが Oleg Kiselyov の場合、「はい」で十分です。そのトピックに関するあなたのエッセイを誰もが見つけることができるからです。それでも、寛大にお願いします。

私が探しているのは、単純な悪い習慣や悪い習慣ではなく、正式なアンチパターンであることに注意してください。

アンチパターンに関するリンクされたウィキペディアの記事から:

...実際のアンチパターンを単純な悪い習慣、悪い習慣、または悪い考えと正式に区別するには、少なくとも2つの重要な要素が存在する必要があります

  1. 最初は有益に見えるが、最終的には有益な結果よりも悪い結果をもたらす行動、プロセス、または構造の繰り返しパターン、および
  2. 明確に文書化され、実際の実践で証明され、再現可能な代替ソリューションが存在します。

さらに、「文書化された」とは、権威ある著者またはよく知られている情報源からのものを意味します。

私が慣れている言語は次のとおりです。

  • Haskell (ここで、コードがコンパイルされれば機能すると本当に考え始めています!)
  • スカラ
  • F#

しかし、他の関数型言語で文書化されているアンチパターンに関する知識を適応させることもできます。

私は Web で多くのことを検索しましたが、私が見つけたすべてのリソースは、OOP または関数レイアウト (関数の先頭に変数を定義するなど...) に関連しています。

4

1 に答える 1

14

私が見た唯一のアンチパターンは過剰なモナド化であり、モナドは信じられないほど便利なので、これは悪い習慣とアンチパターンの中間に位置します。

Pいくつかのオブジェクトについて、true にしたいプロパティがあるとします。オブジェクトを 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
}

さて、ここまでは順調です。これを comonad にするのではなくvalueaにすることで少しごまかしましたが(それが必要な場合)、何でもラップできる便利なラッパーができました。valここで、プロパティ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モナドを効果的に反転するor操作があるので、適切な方法でswap定義していれば、次のようなことができます。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を任せるのではなく、すべてのデコレータを自分で追跡する必要があります。しかし、多くの場合、一連の種類の操作を行う必要はありません。その場合、深くネストされたモナドはアンチパターンです。mapflatMap

(注意: モナドのネスティングにはスタック構造があり、トレイトにはセット構造があります。コンパイラがセットのようなモナドを許可できない固有の理由はありませんが、型理論の典型的な定式化の自然な構成ではありません。アンチパターンはディープ スタックを扱うのは難しいという事実の単純な結果. モナド (または Haskell のモナド トランスフォーマーの標準セット) にすべての Forth スタック操作を実装すると、多少簡単になる可能性があります。

于 2013-04-06T17:17:43.163 に答える