29

下个月我将从事一个新的研发项目,该项目将采用函数式编程语言(我投票支持 Haskell,但现在 F# 获得了更多共识)。现在,我已经使用这些语言有一段时间了,并用它们开发了一些命令行工具,但这是一个相当大的项目,我正在努力提高我的函数式编程知识和技术。我也阅读了很多关于该主题的内容,但我找不到任何记录函数式编程世界中反模式的书籍或资源。

现在,学习反模式意味着了解其他聪明人的失败:在 OOP 中,我知道其中的一些,而且我有足够的经验来明智地选择通常是反模式的东西,完全符合我的需求。但我可以选择这个,因为我知道其他聪明人吸取的教训。

因此,我的问题是:函数式编程中是否有任何记录在案 的反模式?到目前为止,我所有的同事都告诉我他们不知道,但他们无法说明原因。

  • 如果是,请包含一个指向权威来源(目录、论文、书籍或同等材料)的链接。
  • 如果不是,请用适当的定理支持您的回答。

不要把这个问题放在一个列表中:这是一个布尔问题,只需要一个证明来评估答案。例如,如果您是 Oleg Kiselyov,“是”就足够了,因为每个人都可以找到您关于该主题的文章。不过,请大方一点。

请注意,我正在寻找正式的反模式,而不是简单的坏习惯或坏做法。

来自关于反模式的链接维基百科文章

...必须至少存在两个关键要素才能正式区分实际的反模式与简单的坏习惯、坏做法或坏主意

  1. 一些重复的行动模式、过程或结构,最初看起来是有益的,但最终产生的不良后果多于有益结果,以及
  2. 存在一种替代解决方案,该解决方案有明确的记录,在实际实践中得到证明并且可重复。

此外,“记录”是指来自权威作者知名来源的东西。

我习惯的语言是:

  • Haskell(我真的开始认为如果代码编译,它就可以工作!)
  • 斯卡拉
  • F#

但我也可以调整有关以其他功能语言记录的反模式的知识。

我在网上搜索了很多,但我找到的所有资源要么与 OOP 相关,要么与函数布局相关(在函数开头定义变量,等等......)。

4

1 回答 1

14

我见过的唯一反模式是过度单子化,由于单子非常有用,所以它介于不良实践和反模式之间。

假设你有一些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
}

好的,到目前为止一切顺利;我们通过制作valueaval而不是将其制作为共单子(如果这是我们想要的)来作弊,但我们现在有一个方便的包装器,我们可以在其中包装任何东西。现在让我们假设我们也有属性QR

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)是行不通的。有traverseorswap操作可以有效地翻转 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 堆栈操作,它们可能会更容易一些。)

于 2013-04-06T17:17:43.163 回答