Does functional programming replace GoF design patterns?
自从我去年开始学习FAY和OCAML以来,我已经阅读了大量的文章,这些文章坚持设计模式(尤其是Java)是命令语言中缺少的功能的解决方案。我发现的一篇文章提出了相当强烈的主张:
Most people I've met have read the
Design Patterns book by the Gang of
Four. Any self respecting programmer
will tell you that the book is
language agnostic and the patterns
apply to software engineering in
general, regardless of which language
you use. This is a noble claim.
Unfortunately it is far removed from
the truth.Functional languages are extremely
expressive. In a functional language
one does not need design patterns
because the language is likely so high
level, you end up programming in
concepts that eliminate design
patterns all together.
函数编程的主要特性包括作为第一类值、当前值、不可变值等的函数。在我看来,OO设计模式与这些特性中的任何一个都不太明显。
此外,在支持OOP的功能语言(如F和OCAML)中,在我看来很明显,使用这些语言的程序员将使用其他所有OOP语言都可以使用的相同设计模式。事实上,我现在每天都使用f*和OcAML,我在这些语言中使用的模式与我在Java中使用的模式之间没有显著的区别。
函数式编程消除了OOP设计模式的需求,这一说法是否属实?如果是这样,您可以发布或链接到典型OOP设计模式及其功能等效的示例吗?
你引用的博客文章有点夸大了它的说法。FP并没有消除对设计模式的需求。术语"设计模式"并没有被广泛地用于描述FP语言中的相同事物。但它们存在。函数语言有许多最佳实践规则,形式是"当遇到问题x时,使用看起来像y的代码",这基本上就是设计模式。好的。
然而,大多数面向对象的特定设计模式在函数语言中几乎是不相关的,这是正确的。好的。
我不认为说设计模式一般只存在于修补语言中的缺陷上是特别有争议的。如果另一种语言可以解决同样的问题,那么另一种语言就不需要为它设计模式了。该语言的用户甚至可能不知道问题存在,因为,好吧,这在该语言中不是问题。好的。
以下是"四人帮"对这个问题的看法:好的。
The choice of programming language is important because it influences one's point of view. Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called"Inheritance","Encapsulation," and"Polymorphism". Similarly, some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor. In fact, there are enough differences between Smalltalk and C++ to mean that some patterns can be expressed more easily in one language than the other. (See Iterator for example.)
Ok.
(以上引自《设计模式导论》第4页第3段)好的。
The main features of functional
programming include functions as
first-class values, currying,
immutable values, etc. It doesn't seem
obvious to me that OO design patterns
are approximating any of those
features.Ok.
如果不是一级函数的近似值,命令模式是什么?:)在FP语言中,您只需将一个函数作为参数传递给另一个函数即可。在OOP语言中,您必须将函数包装在一个类中,您可以实例化该类,然后将该对象传递给另一个函数。效果是一样的,但在OOP中,它被称为设计模式,需要更多的代码。抽象的工厂模式是什么,如果不流行的话?一次将参数稍微传递给一个函数,以配置当您最终调用它时它会发出什么样的值。好的。
所以是的,有几个GOF设计模式在FP语言中是多余的,因为存在更强大和更容易使用的替代方案。好的。
但是,当然,还有一些设计模式并没有被FP语言解决。FP等价于一个单体的是什么?(暂时不考虑单件通常是一种糟糕的模式)好的。
这两种方法都有效。正如我所说,FP也有它的设计模式,人们通常不这么认为。好的。
但你可能遇到过蒙纳兹。如果不是"处理全球国家"的设计模式,它们是什么?这是一个在OOP语言中非常简单的问题,在那里没有等价的设计模式。好的。
我们不需要"递增静态变量"或"从那个套接字读取"的设计模式,因为这正是您要做的。好的。
说一个monad是一个设计模式和说整数有他们通常的操作一样荒谬,零元素是一个设计模式。不,Monad是一个数学模式,而不是设计模式。好的。
在(纯)函数语言中,副作用和可变状态是不可能的,除非您使用monad"设计模式"或其他允许相同事物的方法来解决它。好的。
Additionally, in functional languages
which support OOP (such as F# and
OCaml), it seems obvious to me that
programmers using these languages
would use the same design patterns
found available to every other OOP
language. In fact, right now I use F#
and OCaml everyday, and there are no
striking differences between the
patterns I use in these languages vs
the patterns I use when I write in
Java.Ok.
也许是因为你还在刻意思考?很多人一生都在处理命令式语言之后,在尝试一种功能性语言时,很难放弃这种习惯。(我在f上看到过一些非常有趣的尝试,实际上每个函数都只是一个"let"语句字符串,基本上就像你使用了一个C程序,并用"let"替换了所有分号。:)好的。
但另一种可能性可能是,您只是没有意识到您正在解决一些琐碎的问题,这些问题需要OOP语言中的设计模式。好的。
当您使用currying或将函数作为参数传递给另一个函数时,请停止并思考如何使用OOP语言来实现这一点。好的。
Is there any truth to the claim that
functional programming eliminates the
need for OOP design patterns?Ok.
是的。:)当您使用FP语言时,您不再需要OOP特定的设计模式。但是您仍然需要一些通用的设计模式,比如MVC或其他非OOP特定的东西,而您需要一些新的特定于FP的"设计模式"。所有语言都有它们的缺点,设计模式通常是我们围绕它们工作的方式。好的。
不管怎样,你可能会发现尝试使用"干净"的FP语言很有趣,比如ML(我个人最喜欢的语言,至少是为了学习目的),或者Haskell,当你面对新事物的时候,你不需要依靠OOP拐杖。好的。
正如所料,一些人反对我将设计模式定义为"修补语言中的缺陷",所以我的理由是:如前所述,大多数设计模式都特定于一个编程范式,有时甚至是一种特定的语言。通常,它们解决只存在于该范式中的问题(参见FP的Monads,或OOP的抽象工厂)。为什么抽象工厂模式不存在于FP中?因为它试图解决的问题并不存在。所以,如果OOP语言中存在问题,而在FP语言中不存在,那么很明显这是OOP语言的一个缺点。这个问题是可以解决的,但是您的语言不这样做,但是需要您提供一组样板代码来解决它。理想情况下,我们希望我们的编程语言能够神奇地消除所有问题。任何仍然存在的问题原则上都存在语言的缺陷。;)好的。好啊。
Is there any truth to the claim that functional programming eliminates the need for OOP design patterns?
函数编程与面向对象编程不同。面向对象的设计模式不适用于函数式编程。相反,您有函数式编程设计模式。
对于函数式编程,您不会阅读OO设计模式书籍,而是阅读其他有关FP设计模式的书籍。
language agnostic
不完全。只有对OO语言不可知的语言。设计模式根本不适用于程序语言。在关系数据库设计环境中,它们几乎没有意义。它们在设计电子表格时不适用。
a typical OOP design pattern and its functional equivalent?
上面不应该存在。这就像要求一段被改写为OO代码的过程代码。嗯…如果我把原来的FORTRAN(或C)翻译成Java,我就没有做过任何翻译。如果我把它完全改写成OO范式,它将不再像原来的Fortran或C——它将无法识别。
从OO设计到功能设计没有简单的映射。他们看待问题的方式非常不同。
函数式编程(像所有的编程风格一样)都有设计模式。关系数据库有设计模式,OO有设计模式,过程编程有设计模式。一切都有设计模式,甚至建筑。
设计模式——作为一个概念——是一种永恒的建筑方式,与技术或问题领域无关。然而,特定的设计模式适用于特定的问题领域和技术。
每个思考他们正在做什么的人都会发现设计模式。
布赖恩对语言和模式之间紧密联系的评论是有意义的,
这个讨论中缺失的部分是成语的概念。Coplien的著作《高级C++》在这里产生了巨大的影响。早在他发现克里斯托弗·亚历山大和没有名字的专栏(你也不能在没有阅读亚历山大的情况下明智地谈论模式)之前,他就谈到了在真正学习一门语言时掌握习语的重要性。他以C语言中的字符串复制为例,而(*from++=*to++);你可以把它看作是缺失语言特性(或库特性)的一种辅助手段,但真正重要的是它是一个比任何部分都更大的思想单位或表达单位。
这就是模式和语言试图做的,让我们更简洁地表达我们的意图。思想的单位越丰富,你所能表达的思想就越复杂。拥有一个丰富的、共享的词汇表,从系统架构到琐碎的工作,可以让我们进行更智能的对话,思考我们应该做什么。
作为个体,我们也可以学习。这是整个练习的重点。我们每个人都能理解和使用我们永远无法想到的东西。语言、框架、库、模式、习语等都在分享知识财富中占有一席之地。
GoF书明确地将自己与OOP联系在一起——标题是设计模式——可重用面向对象软件的元素(Emphasis Mine)。
彼得·诺维格在动态编程中的设计模式对这个主题有着深思熟虑的覆盖,尽管它涉及"动态"语言而不是"功能性"(有重叠)。
下面是另一个链接,讨论这个主题:http://blog.ezyang.com/2010/05/design-patterns-in-haskel/
在他的博客文章中,爱德华描述了所有23种原始的GOF模式。
当你试图从"设计模式"(一般来说)和"FP对OOP"的层次来看待这个问题时,你会发现答案最多只能是模糊的。
不过,在两个轴上都深入一层,考虑到特定的设计模式和特定的语言特性,事情会变得更清楚。
因此,例如,当使用具有代数数据类型和模式匹配、闭包、第一类函数等的语言时,某些特定的模式(如访问者、策略、命令和观察者)肯定会发生更改或消失。不过,GOF手册中的一些其他模式仍然"保留"。
一般来说,我会说,随着时间的推移,特定的模式正在被新的(或只是流行的)语言特性所消除。这是语言设计的自然过程;随着语言变得更高级,以前只能在一本书中使用示例调用的抽象现在成为特定语言特性或库的应用程序。
(旁白:这是我最近写的一篇博客,它还有其他链接,可以更多地讨论FP和设计模式。)
Norvig的演示引述了他们对所有gof模式的分析,他们说23个模式中有16个在函数语言中实现更简单,或者只是语言的一部分。所以大概至少有七种语言是a)同样复杂或b)不存在于语言中。不幸的是,他们没有被列举出来!
我认为很明显,GOF中的大多数"创造性"或"结构化"模式只是为了获取Java或C++中的原始类型系统来做你想做的事情。但其余的都值得考虑,不管你用什么语言编程。
其中一个可能是原型;虽然它是JavaScript的一个基本概念,但它必须在其他语言中从头实现。
我最喜欢的模式之一是空对象模式:将缺少某个对象表示为不执行任何适当操作的对象。这可能更容易在函数语言中建模。然而,真正的成就是视角的转变。
我想说的是,如果您有一种像lisp这样的语言,它支持宏,那么您可以构建自己的领域特定的抽象、抽象,这些抽象通常比一般的惯用法解决方案要好得多。
甚至OO设计模式解决方案都是特定于语言的。设计模式是解决编程语言不能解决的常见问题的解决方案。在Java中,单模式解决了一个简单的问题。在scala中,除了类之外,还有一个名为object的顶级构造。它被懒惰地实例化了,只有一个。你不必使用单例模式来获得单例。它是语言的一部分。
GOF设计模式是为Simula 67的后裔(如Java和C++)编写解决方案。
设计模式处理的大多数"问题"是由以下原因引起的:
- 静态类型的类,它指定对象,但本身不是对象;
- 对单个调度的限制(只有最左边的参数用于选择一个方法,其余的参数仅被视为静态类型:如果它们有动态类型,则由方法使用特殊方法进行排序);
- 常规函数调用和面向对象函数调用之间的区别,这意味着面向对象函数不能作为期望常规函数的函数参数传递,反之亦然;以及
- "基本类型"和"类类型"之间的区别。
这些设计模式中没有一个不会在公共Lisp对象系统中消失,即使解决方案的结构与相应的设计模式基本相同。(此外,该目标系统在过去十年里远远领先于GoF图书。在这本书第一次出版的同一年,通用LISP成为了一个ANSI标准。)
就函数式编程而言,模式是否适用于它取决于给定的函数式编程语言是否具有某种对象系统,以及它是否按照受益于模式的对象系统建模。这种类型的对象定向与函数式编程不能很好地混合,因为状态的突变在前面和中间。
构造和非变异访问与功能编程兼容,因此与抽象访问或构造相关的模式可能适用:模式,如工厂、外观、代理、装饰、访问者。
另一方面,状态和策略等行为模式可能不会直接应用于功能OOP,因为状态突变是它们的核心。这并不意味着它们不适用;也许它们以某种方式与任何可用于模拟可变状态的技巧结合使用。
模式是解决类似问题的方法,这些问题一次又一次地被发现,然后被描述和记录。因此,FP不会取代模式;但是,FP可能会创建新的模式,并使一些当前的"最佳实践"模式"过时"。
正如其他人所说,有一些特定于函数式编程的模式。我认为,摆脱设计模式的问题,与其说是转换为功能性的问题,还不如说是语言特性的问题。
看看scala如何消除"singleton模式":只需声明一个对象而不是类。另一个特性,模式匹配,有助于避免访问者模式的笨拙。请参见下面的比较:http://andymaleh.blogspot.com/2008/04/scalas-pattern-matching-visitor-pattern.html
scala和f_一样,是OO功能的融合。我不知道F,但它可能有这种特性。
闭包存在于函数语言中,但不必局限于它们。它们有助于研究授权者模式。
再观察一次。这段代码实现了一个模式:它是如此经典,而且它是如此基本,以至于我们通常不认为它是"模式",但它确实是:
1 | for(int i = 0; i < myList.size(); i++) { doWhatever(myList.get(i)); } |
诸如Java和C语言的命令式语言采用了本质上是一个功能结构来处理这个问题:"Frach"。
我想插入杰里米·吉本斯的几篇优秀但有点密集的论文:"将模式设计为高阶数据类型通用程序"和"迭代器模式的本质"(这两篇论文都可以在http://www.comlab.ox.ac.uk/jeremy.gibbons/publications/上找到)。
这两者都描述了惯用功能构造如何覆盖其他(面向对象)设置中特定设计模式所覆盖的地形。
如果不提出类型系统,就无法进行此讨论。
The main features of functional programming include functions as first-class values, currying, immutable values, etc. It doesn't seem obvious to me that OO design patterns are approximating any of those features.
这是因为这些功能不能解决OOP所解决的问题…它们是命令式编程的替代方案。OOP的关键在于ML和Haskell的类型系统。具体来说,sum类型、抽象数据类型、ml模块、haskell类型类。
But of course there are still design patterns which are not solved by FP languages. What is the FP equivalent of a singleton? (Disregarding for a moment that singletons are generally a terrible pattern to use)
typeclasses做的第一件事就是消除对单例的需求。
你可以通过23个名单,删除更多,但我现在没有时间。
基本上,是的!
- 当一个模式绕开了缺失的特性(高阶函数、流处理…)时,这将极大地促进组合。
- 一次又一次地重写模式实现的需要本身就是一种语言味道。
此外,如果你愿意的话,这个页面(aredesignpatternsmissinganguagefeatures)提供了一个"模式/功能"翻译表和一些不错的讨论。
我认为只有两个GOF设计模式被设计来将函数编程逻辑引入自然的OO语言。我考虑战略和指挥。其他一些GOF设计模式可以通过函数编程进行修改,以简化设计并保持其目的。
OOP和GOF模式处理状态。OOP模拟现实,使代码库尽可能接近给定的现实需求。GOF设计模式是为解决原子现实问题而确定的模式。他们用语义的方式处理状态问题。
正如在真正的函数编程中不存在状态一样,应用GOF模式是没有意义的。没有功能设计模式与gof设计模式相同。每一个功能设计模式都是人工的,与现实不同,因为功能是数学的构造,而不是现实。
函数缺少时间概念,因为无论当前时间是什么,它们总是返回相同的值,除非时间是函数参数的一部分,这使得处理"未来请求"非常困难。混合语言混合了这些概念,使得语言不是真正的函数式编程语言。
函数语言的兴起仅仅是因为一件事:当前物理的自然限制。由于物理规律的原因,当今的处理器在处理指令的速度上受到限制。您可以看到时钟频率的停滞,但处理核心的扩展。这就是为什么并行指令对于提高现代应用程序的速度变得越来越重要的原因。根据定义,函数编程没有状态,因此没有副作用,因此安全地并行处理函数是安全的。
GOF模式并不过时。它们至少是模拟现实需求所必需的。但是如果你使用一种函数式编程语言,你必须把它们转换成它们的混合等价物。最后,如果使用持久性,就没有机会只生成功能性程序。对于程序的混合元素,仍然需要使用GOF模式。任何其他纯功能的元素都不需要使用gof模式,因为没有状态。
因为GOF模式对于真正的函数式编程是不必要的,这并不意味着不应该应用可靠的原则。坚实的原则超越了任何语言范式。
在2013年出版的名为"函数式编程模式-在scala和clojure中"的新书中,作者michael.b.linn在许多情况下对gof模式进行了比较并提供了替换,还讨论了诸如"尾递归"、"memoization"、"lazy sequence"等较新的函数模式。
这本书在亚马逊上有售。我发现这是非常有益的和鼓舞人心的,当来自一个OO背景的几十年。
在函数式编程中,设计模式有着不同的含义,事实上,大多数OOP设计模式在函数式编程中是不必要的,因为抽象的层次更高,HOF用作构建块。
The principle of an HOF means that functions can be passed as
arguments to other functions. and functions can return values.
函数式编程不能取代设计模式。无法替换设计模式。
模式简单地存在;它们随着时间的推移而出现。共和党人的书使其中一些人正式化了。如果随着开发人员使用令人兴奋的函数式编程语言,新的模式出现了,也许还会有关于它们的书。
OOP和FP有不同的目标,OOP的目标是封装软件组件的复杂性/移动部分,而FP的目标是最小化软件组件的复杂性和依赖性,但是这两个范例并不一定100%矛盾,可以一起应用以从两个世界中获益。即使语言本身不支持C这样的函数式编程,如果您了解FP原则,也可以编写函数式代码;同样,如果您了解OOP原则、模式和最佳实践,也可以使用F应用OOP原则。您将根据您试图解决的情况和问题做出正确的选择,而不管您使用的编程语言是什么。
我认为每种模式都有不同的目的,因此不能用这种方式进行比较。
我没有听说gof设计模式适用于每种语言。我听说它们适用于所有OOP语言。如果您使用函数式编程,那么您所解决的问题领域就不同于OO语言。
我不会用函数语言来编写用户界面,但像C语言或Java这样的OO语言会使这个工作更容易。如果我正在编写一种函数式语言,那么我就不会考虑使用OO设计模式。
正如公认的答案所说,OOP和FP都有其特定的模式。
但是,有一些模式非常常见,我能想到的所有编程平台都应该有。以下是(不完整)列表:
适配器。我很难想到一个有用的编程平台,它是如此全面(并且自我实现),以至于不需要与世界对话。如果要这样做,肯定需要一个适配器。
Fa?艾德。任何能够处理大型源代码的编程平台都应该能够模块化。如果要为程序的其他部分创建一个模块,您需要隐藏代码的"脏"部分,并为其提供一个良好的接口。
解释器。一般来说,任何程序都只做两件事:解析输入和打印输出。鼠标输入需要解析,窗口小部件需要打印出来。因此,有一个嵌入式的解释器给程序额外的能力来定制东西。
另外,我注意到在典型的fp语言haskell中,有一些类似于gof模式的东西,但是有不同的名称。在我看来,这表明它们存在,因为在FP和OOP语言中都有一些共同的问题需要解决。
- Monad变压器和装饰器。前者用于向现有monad中添加附加功能,后者用于向现有对象添加附加功能。