函数式编程是否有任何记录的反模式?

Gia*_*sio 29 f# haskell functional-programming scala anti-patterns

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

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

因此,我的问题是:函数式编程中是否有任何记录的 反模式?直到现在,我的所有同事告诉我他们不知道,但他们不能说明原因.

  • 如果是,请包含一个指向权威来源的链接(目录,论文,书籍或同等学历).
  • 如果不是,请用适当的定理支持你的答案.

不要在列表中转换这个问题:这是一个布尔问题,只需要一个证明来评估答案.例如,如果你是Oleg Kiselyov,"是"就足够了,因为每个人都能找到你关于这个主题的文章.不过,请慷慨.

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

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

...... 必须至少有两个关键要素才能正式区分实际的反模式和简单的坏习惯,不良做法或坏主意:

  1. 一些重复的行动模式,过程或结构最初似乎是有益的,但最终会产生比有益结果更糟糕的后果,并且
  2. 存在一种明确记录的替代解决方案,在实际操作中得到证实并且可重复.

此外,通过"记录",我的意思是来自权威作者众所周知的来源.

我习惯的语言是:

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

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

我在网上搜索了很多,但我发现的所有资源都与OOP或函数布局相关(在函数开头定义变量等等).

Rex*_*err 14

我看到的唯一的反模式是过度的monadization,并且由于monad可以非常有用,所以它介于一个糟糕的实践和一个反模式之间.

假设你有一些属性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
}
Run Code Online (Sandbox Code Playgroud)

好的,到目前为止一切顺利; 我们被骗了一点点通过使value一个val,而不是使之成为一个comonad(如果这就是我们想要的),但我们现在有一个方便的包装,使我们可以换什么.现在让我们假设我们也有属性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) 
}
Run Code Online (Sandbox Code Playgroud)

所以我们装饰我们的对象:

class Foo { override def toString = "foo" }
val bippy = R.pure( Q.pure( P.pure( new Foo ) ) )
Run Code Online (Sandbox Code Playgroud)

现在我们突然遇到了许多问题.如果我们有一个需要财产的方法,我们该Q怎么做呢?

def bar(qf: Q[Foo]) = qf.value.toString + "bar"
Run Code Online (Sandbox Code Playgroud)

好吧,显然bar(bippy)不会起作用.有些traverseswap操作可以有效地翻转monad,所以如果我们swap以适当的方式定义,我们可以做类似的事情

bippy.map(_.swap).map(_.map(bar))
Run Code Online (Sandbox Code Playgroud)

得到我们的字符串(实际上,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)
Run Code Online (Sandbox Code Playgroud)

呼!这么容易多了.现在指出并非一切都更容易是非常重要的.例如,使用此方法,如果您开始操作Foo,则必须自己跟踪所有装饰器,而不是让monadic map/ flatMap为您执行操作.但很多时候,你并不需要做一堆实物操作,然后深深嵌套单子是一个反模式.

(注意:monadic嵌套具有堆栈结构,而traits具有set结构;没有固有的原因,为什么编译器不能允许类似set的monad,但它不是典型的类型理论表达式的自然构造.反模式是深堆栈很难处理这个事实的简单结果.如果你为monad(或Haskell中标准的Monad变换器集)实现所有Forth堆栈操作,它们会更容易一些.)

  • Monads用于封装某些方面(例如,List封装非确定数量的元素,Option封装可选性,Future封装异步),然后将其作为用户的关注点删除.Scala不像Haskell那样使用monad进行编程,但是尽管成本很高,它们仍然可能是一个重要的好处.这个例子很难重复地重复ID monad,没有任何价值,简单地说,不增加成本没有任何好处.我是一个稻草人. (8认同)
  • @GiacomoTesio - 我也希望得到其他答案. (3认同)