下个月,我将参与一个新的R& D项目,该项目将采用函数式编程语言(我投票支持Haskell,但现在F#得到了更多的共识)。现在,我已经使用过这些语言一段时间了,并用它们开发了一些命令行工具,但这是一个非常大的项目,我正在努力提高我的函数式编程知识和技术。我也阅读了很多关于这个主题的内容,但是我找不到任何记录功能编程领域反模式的书籍或资源。
现在,了解反模式意味着了解其他聪明人的失败:在OOP我知道其中的一些,而且我有足够的经验可以明智地选择什么时候通常是反对的 - 模式,完全符合我的需求。但我可以选择这个,因为我知道其他聪明人所吸取的教训。
因此,我的问题是:函数式编程中是否有记录 anti-patterns?直到现在,我的所有同事告诉我他们不知道,但他们不能说明原因。
请 don't turn this question in a list:这是一个布尔问题,只需要一个证明来评估答案。例如,如果你是Oleg Kiselyov,“是”就足够了,因为每个人都能找到你关于这个主题的文章。不过,请慷慨。
请注意,我正在寻找正式的反模式,而不是简单的坏习惯或不良做法。
来自linked wikipedia article on Anti-Patterns:
... 必须至少有两个关键元素来正式区分实际的反模式与简单的坏习惯,不良做法或坏主意:
- 一些重复的行动,过程或结构模式最初似乎是有益的,但最终会产生比有益结果更糟糕的后果,
- 存在一种明确记录的替代解决方案,在实际操作中得到验证并且可重复使用。
醇>
此外,“记录”是指来自权威作者或众所周知的来源的内容。
我习惯的语言是:
但我也可以调整其他功能语言中记录的反模式知识。
我在网上搜索了很多,但我找到的所有资源都与OOP或功能布局相关(在函数开头定义变量等等)。
答案 0 :(得分:14)
我见过的唯一反模式是过度蒙版化,而且由于monad可能非常有用,所以它介于不良实践和反模式之间。
假设您有一些属性P
,您希望对某些对象有效。你可以使用P monad来装饰你的对象(在Scala中,在REPL中使用paste
来使对象及其伴侣粘在一起):
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
成为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
个操作可以有效地翻转monad,因此如果我们以适当的方式定义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
,则必须自己跟踪所有装饰器,而不是让monadic map
/ flatMap
为您执行此操作。但通常你不需要做一堆实物操作,然后深度嵌套的monad是反模式。
(注意:monadic嵌套具有堆栈结构,而traits具有set结构;没有固有的原因,为什么编译器不能允许类似set的monad,但它不是典型的类型理论公式的自然构造。 -pattern是一个简单的结果,即深度堆栈很难处理。如果你为monad(或者Haskell中标准的Monad变换器集)实现所有Forth堆栈操作,它们会更容易一些。)