是否有用于功能编程的软件工程方法?[关闭]


203

今天所讲授的软件工程完全专注于面向对象的编程和“自然”的面向对象的世界观。有一种详细的方法学描述了如何将域模型转换为具有几个步骤和许多(UML)工件(如用例图或类图)的类模型。许多程序员已经内化了这种方法,并且对如何从头开始设计面向对象的应用程序有一个很好的主意。

新的炒作是函数式编程,许多书籍和教程都对此进行了讲授。但是功能软件工程呢?在阅读有关Lisp和Clojure的内容时,我得出了两个有趣的陈述:

  1. 功能程序通常是自下而上而不是自上而下的开发(“ On Lisp”,Paul Graham)

  2. 功能程序员使用Map,而OO程序员则使用对象/类(“ Rich Hickley的“ Java程序员的Clojure”)。

那么,对功能应用程序(即Lisp或Clojure)进行系统(基于模型?)设计的方法是什么?常见的步骤是什么,我使用什么工件,如何将它们从问题空间映射到解决方案空间?


3
我在这里发表评论:许多程序都是以自上而下的方式编写的,在“并发清理中的功能编程”一书中给出了使用功能性语言进行软件开发过程的实用说明(该语言本身非常学术,虽然)。
Artyom Shalkhakov 2011年

4
1.帕纳斯(Parnas)认为,大多数程序应自下而上,然后伪装成自上而下的样子,因此应混合使用这些方法,没有正确的答案。
2011年

2
2.对象根据其封装的结构化状态提供行为,在FP中,所有状态和结构都是显式的,并且行为(功能)与结构分离。因此,对于数据建模,您可以使用对象映射,但是在设计应用程序时,不能用函数替换对象-FP是通过管道生成和评估的大型表达式,OOP是关于创建模型并在对象之间发送消息的。
2011年

1
有时我问了一个相关的问题:“如何从Clojure中的关系数据库中获得一个模型数据?” stackoverflow.com/questions/3067261/...
桑迪普

4
在SICP讲座中,Hal Abelson笑着说:“有一种著名的方法论,或者我应该说神话,叫做软件工程,它制作了复杂的图和需求,然后构建系统;他们没有太多编程经验”。我来自一所“ Java学校”,在很长一段时间里,我们都教过UML,人工制品和东西,尽管其中的一些很好,但过多的计划和计谋(双关语)却无济于事:您永远不会知道自己如何软件直到您真正开始编码为止。
lfborjas 2011年

Answers:


165

感谢上帝,软件工程人员尚未发现功能编程。这里有一些相似之处:

  • 许多OO“设计模式”被捕获为高阶函数。例如,“访问者”模式在功能界被称为“折叠”(或者,如果您是一个尖尖的理论家,则是“变形”)。在功能语言中,数据类型主要是树或元组,并且每种树类型都具有与之相关的自然分类。

    这些高阶函数通常带有某些编程定律,也称为“自由定理”。

  • 功能性程序员使用图的方式比面向对象程序员少得多。OO图中表示的大部分内容都以类型或“签名”表示,您应该将其视为“模块类型”。Haskell还具有“类型类”,这有点像接口类型。

    那些使用类型的函数式程序员通常认为“一旦正确地使用了类型,代码便会编写自身。”

    并非所有功能语言都使用显式类型,但是How to Design Programs(如何设计程序)一书是学习Scheme / Lisp / Clojure的优秀书籍,很大程度上依赖于与类型紧密相关的“数据描述”。

那么,对功能应用程序(即Lisp或Clojure)进行系统(基于模型?)设计的方法是什么?

任何基于数据抽象的设计方法都可以正常工作。我碰巧认为,当语言具有显式类型时,这会更容易,但即使没有,它也可以工作。关于设计方法的抽象数据类型,这是很容易适应函数式编程,良好的开端是抽象和规范的程序开发芭芭拉Liskov的和约翰·加塔的第一版本。里斯科夫在这项工作中获得了图灵奖。

Lisp独有的另一种设计方法是,确定哪种语言扩展在您工作所在的问题领域中将很有用,然后使用卫生宏将这些构造添加到您的语言中。关于这种设计的一个好地方是Matthew Flatt的文章在Racket中创建语言。该文章可能在收费墙后面。您还可以通过搜索“特定于领域的嵌入式语言”一词找到有关这种设计的更多通用材料。除了Matthew Flatt所涵盖的特定建议和示例之外,我可能会从Graham的On Lisp开始ANSI Common Lisp开始

常见的步骤是什么,我要使用哪些工件?

常用步骤:

  1. 识别程序中的数据及其上的操作,并定义代表该数据的抽象数据类型。

  2. 确定常见的运算或计算模式,并将其表示为高阶函数或宏。期望将这一步骤作为重构的一部分。

  3. 如果您使用的是类型化的功能语言,请尽早并经常使用类型检查器。如果您使用的是Lisp或Clojure,则最佳实践是首先编写包括单元测试在内的功能合同,这是最大程度地由测试驱动的开发。您将要使用已移植到平台的任何版本的QuickCheck,在您的情况下,它看起来像是ClojureCheck。它是一个功能强大的库,用于构建使用高阶函数的代码的随机测试。


2
IMO访客不是折叠的-折叠是访客的子集。多重分发不会(直接)通过折叠捕获。
Michael Ekstrand

6
@Michael-实际上,您可以非常巧妙地捕获具有各种高阶同构关系的多个调度。杰里米·吉本斯(Jeremy Gibbons)的工作是寻找这一点的地方,但是我通常建议对数据类型通用编程的工作-我特别喜欢compos论文。
sclv 2011年

6
我同意我认为图表用于描述功能设计的频率要低得多,我认为这很可惜。当使用大量的HOF时,很难表现出等效的时序图。但是我希望可以更好地探索如何用图片描述功能设计的空间。尽管我讨厌UML(作为规范),但我发现UML(作为草图)在Java中非常有用,并希望有最佳实践来实现等效功能。我一直在尝试使用Clojure协议和记录进行此操作,但是我什么都不喜欢。
Alex Miller

22
+1表示“感谢上帝,软件工程师尚未发现功能编程。” ;)
Aky 2011年

1
OO本身就是尝试使用类型进行编程的方法,因此方法并没有太大不同。OO设计的问题通常似乎源于人们不知道自己在做什么。
Marcin 2012年

46

对于Clojure,我建议回到良好的旧关系模型。在Tar​​pit外面是一本鼓舞人心的读物。


那是一篇很棒的文章,当所有这些概念都存活到今天的复兴之时,计算机科学的美好时光一定真的非常出色。这可能是由于数学基础扎实。
Thorsten

1
这个。这个。这个!我正在阅读本文,这真的很有趣,它似乎涵盖了构建实际系统所需的所有基础,同时又以高度受控的方式保持最小的可变状态。我想以FRelP风格构建Pong和Tetris(请问奇怪的缩写是什么,但是已经有另一个更流行的FRP:函数式反应性编程)。
约翰·克罗马蒂

阅读本文后,我认为Clojure至少对于基本逻辑意外状态和控制以及其他组件而言,将是FR(el)P的理想语言。我想知道如何在不重新发明sql的情况下(无缺陷)在clojure 中对基本状态进行关系定义?还是仅使用一个良好的关系(sql)数据库并在其之上构建一个功能程序而没有OOP引入的概念上的不匹配的想法?
Thorsten

1
@Thorten的基本思想是set = table,map = index。困难的部分是保持索引和表同步,但是可以使用更好的集合类型解决此问题。我实现的一个简单的集合类型是键集,它是使用键功能测试唯一性的集。这意味着连接一个值插入或更新,并使用主键字段调用get会返回整行。
cgrand


38

我个人发现,面向对象开发中的所有通常的良好做法也适用于函数式编程-只需进行一些小的调整即可考虑函数式世界观。从方法论的角度来看,您实际上不需要做任何根本不同的事情。

我的经验来自于近年来从Java迁移到Clojure。

一些例子:

  • 了解您的业务领域/数据模型 -无论您要设计对象模型还是使用嵌套映射创建功能性数据结构,都同样重要。在某些方面,FP会更容易,因为它鼓励您将数据模型与功能/过程分开考虑,但您仍然必须同时考虑两者。

  • 设计中的面向服务 -从FP角度来看,实际上效果很好,因为典型的服务实际上只是具有某些副作用的功能。我认为,Lisp世界中有时拥护的软件开发的“自下而上”观点实际上只是另一种形式的良好的面向服务的API设计原则。

  • 测试驱动开发 -在FP语言中效果很好,实际上有时甚至更好,因为纯函数非常适合编写清晰,可重复的测试,而无需建立有状态的环境。您可能还希望构建单独的测试以检查数据完整性(例如,此映射是否具有我期望的所有键,以平衡在OO语言中类定义将在编译时为您强制执行的事实)。

  • 原型制作/迭代 -与FP一样有效。如果您非常擅长构建工具/ DSL并在REPL上使用它们,则甚至可以与用户一起制作原型。


3
这些做法对我来说听起来很熟悉。我仍然认为,应该由Bruegge / Dutoit撰写等效于“使用UML,模式和Java的面向对象软件工程”的功能,而不是撰写第六本书“ Clojure中的编程”。它可以被称为“使用Clojure和“什么”的功能软件工程”。他们在FP中使用UML和模式吗?我记得Paul Graham写道,模式是Lisp中缺乏抽象的标志,应该通过引入新的宏来弥补。
托尔斯滕

3
但是,如果将模式转换为最佳实践,则FP世界中也可能存在模式,值得与未初始化的模式共享。
Thorsten

2
PAIP书中有一些有趣的原理设计。norvig.com/paip.html
mathk 2011年

1
还有函数式编程模式(递归等)
GabrielŠčerbák2011年

13

OO编程将数据与行为紧密耦合。函数式编程将两者分开。因此,您没有类图,但是您有数据结构,尤其是代数数据类型。可以将这些类型编写为与您的域非常匹配,包括通过构造消除不可能的值。

因此,没有书籍和书籍,但是有一种行之有效的方法,正如俗语所说,使不可能的价值无法代表。

这样,您可以选择将某些类型的数据表示为函数,或者相反,将某些功能表示为数据类型的并集,以便进行选择,例如,序列化,更严格的规范,优化等。 。

然后,鉴于此,您可以在adts上编写函数,以便建立某种 代数 -即,有固定的定律适用于这些函数。有些可能是幂等的-在多次应用后相同。有些是联想的。有些是可传递的,等等。

现在,您有了一个域,在该域​​上有可以根据行为规律进行组合的功能。一个简单的嵌入式DSL!

哦,有了给定的属性,您当然可以为它们编写自动随机测试(ala QuickCheck)..这仅仅是开始。


1
使不可能的值无法表示的方法不太适用于像Clojure和Scheme这样的动态类型的语言,而不适用于像Haskell和ML这样的静态类型的语言。
Zak

@Zak-好吧,您不能静态检查它们是否不可表示,但是无论如何您都可以用相同的方式构建数据结构。
sclv 2011年

7

面向对象的设计与软件工程不是一回事。软件工程与我们从需求到工作系统的准时,低缺陷率的整个过程有关。函数式编程可能不同于OO,但是它并不能消除需求,高级和详细的设计,验证和测试,软件指标,估计以及所有其他“软件工程”。

此外,功能程序确实具有模块化和其他结构。您的详细设计必须根据该结构中的概念来表达。


5

一种方法是在选择的功能编程语言中创建内部DSL。然后,“模型”是在DSL中表达的一组业务规则。


1
我理解的方法是,首先将语言构建到问题域,直到达到抽象级别为止,即与解决该抽象问题相比,代码中不再出现重复模式。
Thorsten

1
但是,当“模型是DSL中表达的一组业务规则”时,它看起来如何?在Java EE应用程序中,模型被编写为POJO实体,从控制器EJB调用该实体,例如,依次更新视图JSP。FP中是否有类似的架构模式(例如MVC模式)?看起来怎么样?
Thorsten

2
完全没有理由在FP中没有MVC模式。FP仍然可以让您构建丰富的数据结构,并且可以说可以通过ADT和模式匹配来构建更丰富的数据结构。如果有的话,由于FP将数据和行为分开,因此MVC类型的系统更自然地出现。
sclv 2011年

5

请参阅我对另一篇文章的回答:

Clojure如何消除关注点分离?

我同意需要就如何使用FP方法构建大型应用程序这一主题编写更多的文章(此外,还需要做更多工作来记录FP驱动的UI)


3
我喜欢90%的管道和10%的宏方法。将功能程序视为对不变数据进行转换的管道似乎很自然。我不确定我是否理解“将所有情报放入数据,而不是代码中”的意思,因为在一种数据结构上使用100个函数(而不是在10个数据结构上使用10个函数)的方法似乎暗示相反。因为OOP中的数据结构内置了自己的行为,所以它们不是比FP中的数据结构更智能吗?
Thorsten

3

尽管这可能被认为是幼稚和简单的,但我认为“设计秘诀”(Felleisen等人在其HtDP的书中提倡的一种系统化的解决问题的方法应用于编程)将与您所寻找的接近。

这里有几个链接:

http://www.northeastern.edu/magazine/0301/programming.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371


指向东北页面的链接似乎已死。
James Kingsbery 2013年

1
詹姆斯,你是对的,不幸的是,我不记得里面有什么要修复的。我只知道HtDP的作者继续创建Pyret语言(并且可能正在修订HtDP的第二版以使用它而不是Racket(以前是PLT Scheme))。
Artyom Shalkhakov

3

我最近找到了这本书: 功能和反应域建模

我认为这完全符合您的问题。

从书中描述:

功能和反应域建模教会您如何从纯函数的角度思考域模型,以及如何组合它们以构建更大的抽象。您将以函数式编程的基础知识开始,然后逐步发展为实现复杂域模型所需了解的高级概念和模式。该书演示了高级FP模式(如代数数据类型,基于类型类的设计以及副作用的隔离)如何使您的模型具有可读性和可验证性。


2

理查德·伯德教授和英国牛津大学的程序代数小组有一种“程序计算” /“按设计设计”的风格,我认为考虑到这种方法论并不为过。

就我个人而言,尽管我喜欢AoP小组制作的作品,但我自己却没有纪律以这种方式实践设计。但这是我的缺点,而不是程序计算之一。


2

我发现行为驱动开发非常适合在Clojure和SBCL中快速开发代码。将BDD与功能语言结合使用的真正好处是,与使用过程语言相比,我倾向于编写比平时更精细的粒度单元测试,因为我将问题分解成较小的功能块要好得多。


您使用什么工具在Clojure中进行BDD?
murtaza52

我喜欢Midje。它是最新的,非常富有表现力。看看:github.com/marick/Midje
Marc

1

老实说,如果您想为功能程序设计配方,请看一下Haskell的Prelude之类的标准功能库。在FP中,模式通常是由高阶过程(在函数上运行的函数)本身捕获的。因此,如果看到一个模式,通常会简单地创建一个高阶函数来捕获该模式。

fmap是一个很好的例子。此函数将一个函数作为参数,并将其应用于第二个参数的所有“元素”。由于它是Functor类型类的一部分,因此Functor的任何实例(例如列表,图形等)都可以作为第二个参数传递给此函数。它捕获了将函数应用于第二个参数的每个元素的一般行为。


-7

好,

通常,许多功能性编程语言在大学中长期用于“小玩具问题”。

由于OOP由于“状态”而难以进行“并行编程”,因此它们现在变得越来越流行。有时,函数样式更适合解决诸如Google MapReduce这样的问题。

我确信,当功能人员碰壁时[尝试实现大于1.000.000行代码的系统],其中一些人会附带带有流行词:-)的新软件工程方法。他们应该回答一个古老的问题:如何将系统分成多个部分,以便我们一次可以“咬”每个部分?[使用功能性样式进行工作迭代,确定性的进化方式]。

可以肯定的是,功能样式将影响我们的面向对象样式。我们从功能系统中“仍然”提取了许多概念,并适应了我们的OOP语言。

但是功能程序会用于如此大的系统吗?它们会成为主流吗?那是问题

如果没有实施如此庞大的系统,没人会想到现实的方法论,这会使他的手变得肮脏。首先,您应该弄脏双手,然后提出解决方案。解决方案-没有“真正的痛苦和污垢”的建议将是“幻想”。


现在已经有足够多的使用功能语言构建的大型系统。即使没有,这也不是争论。
Svante

好吧,给他们一些名字?我只知道很少的“ Erlang”系统。[中等大小]但是Haskel?Clojure?Lisp?
Hippias Minor

而且,[编写大型系统]是真正的理由。因为那是测试用例。这个测试案例表明,如果这种功能风格有用,并且我们可以在现实世界中用它来做一些实际的事情。
Hippias Minor

2
关于语言而不是“ OOP”的有趣之处在于,它们通常使您摆脱“设计方法徽标”的思考,自己思考,并以最适当的方式削减程序,而不是盲目地遵循既定模式并与之共处。官僚样板。抱歉,这里没有10点的3周课程。
Svante

1
我看过你不相信的事情。
Svante
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.