函数式编程有软件工程方法论吗?

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

新的宣传是函数式编程,在许多书籍和教程中都有教授。但是功能性软件工程呢? 在阅读Lisp和Clojure时,我发现了两个有趣的语句:

  1. 函数式程序通常是自底向上而不是自顶向下开发的(《论Lisp》,Paul Graham)

  2. 函数式程序员使用映射,而oop程序员使用对象/类(《Clojure for Java Programmers》,Rich Hickley谈话)。

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

23188 次浏览

一种方法是在所选择的函数式编程语言中创建内部DSL。“模型”是用DSL表示的一组业务规则。

对于Clojure,我建议回到良好的旧式关系建模。走出沥青坑是一本励志读物。

就我个人而言,我发现来自OO开发的所有常见的良好实践也适用于函数式编程——只是在考虑函数式世界观时做了一些小调整。从方法论的角度来看,您实际上不需要做任何根本不同的事情。

我的经验来自于最近几年从Java转移到Clojure。

一些例子:

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

  • 设计中的面向服务 -从FP的角度来看,实际上工作得非常好,因为典型的服务实际上只是一个带有一些副作用的函数。我认为,在Lisp世界中有时信奉的“自底向上”的软件开发观点实际上只是另一种伪装下的良好的面向服务的API设计原则。

  • -在FP语言中工作得很好,事实上有时甚至更好,因为纯函数本身非常适合编写清晰、可重复的测试,而不需要设置有状态的环境。你可能还想构建单独的测试来检查数据的完整性(例如,这个映射中是否有我期望的所有键,以平衡在OO语言中类定义会在编译时为你强制执行这一事实)。

  • 原型/迭代 -与FP一样有效。如果你非常擅长构建工具/ DSL并在REPL中使用它们,你甚至可以与用户一起进行原型开发。

虽然这可能被认为是天真和简单的,但我认为“设计食谱”(Felleisen等人在他们的书HtDP中提倡的应用于编程的系统解决问题的方法)将接近你似乎在寻找的东西。

这里有一些链接:

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

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

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

所以没有关于这个问题的书籍,但有一种行之有效的方法,正如俗话所说,让不可能的价值观变得不可代表。

在这样做的过程中,您可以做出一系列选择,将某些类型的数据表示为函数,反之,将某些函数表示为数据类型的联合,这样您就可以获得,例如序列化、更严格的规范、优化等。

然后,考虑到这一点,你在adts上写函数,这样你就建立了某种代数——也就是说,对于这些函数有固定的定律。有些可能是等幂的,多次应用后是一样的。有些是结合性的。有些是传递性的,等等。

现在你有了一个定义域,在这个定义域上,你有了函数,这些函数根据良好的规律组成。一个简单的嵌入式DSL!

哦,对了,给定属性,你当然可以编写自动随机测试。而这仅仅是个开始。

感谢上帝,软件工程人员还没有发现函数式编程。这里有一些相似之处:

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

    这些高阶函数通常带有特定的编程法则,也就是“自由定理”

  • 函数式程序员比面向对象程序员更少地使用图。在OO图中表达的大部分内容都是用类型或“签名”来表达的,你应该把它们看作“模块类型”。Haskell也有“类型类”,这有点像接口类型。

    那些使用类型的函数式程序员通常认为“一旦你得到了正确的类型;代码实际上是自己编写的。”

    并不是所有的函数式语言都使用显式类型,但是如何设计程序这本书,一本学习Scheme/Lisp/Clojure的优秀书籍,很大程度上依赖于“数据描述”,这与类型密切相关

那么,在Lisp或Clojure中,系统地(基于模型的)设计功能应用程序的方法是什么呢?

任何基于数据抽象的设计方法都可以很好地工作。我碰巧认为,当语言有显式类型时,这更容易,但即使没有显式类型,它也可以工作。一本关于抽象数据类型的设计方法的好书,很容易适应函数式编程,是Barbara Liskov和John Guttag编写的程序开发中的抽象和规范第一个版。利斯科夫获得图灵奖的部分原因就是这项工作。

Lisp特有的另一种设计方法是确定哪些语言扩展在您工作的问题域中有用,然后使用卫生宏将这些结构添加到您的语言中。阅读这种设计的一个好地方是Matthew Flatt的文章在球拍中创建语言。文章可能在付费墙后面。你也可以通过搜索“领域特定的嵌入式语言”来找到更多关于这种设计的一般材料;对于Matthew Flatt所涵盖之外的特别建议和例子,我可能会从Graham的在Lisp < em > < / em >ANSI Common Lisp开始。

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

常见的步骤:

  1. 识别程序中的数据和对其进行的操作,并定义表示此数据的抽象数据类型。

  2. 识别常见的操作或计算模式,并将它们表示为高阶函数或宏。这一步是重构的一部分。

  3. 如果您使用的是类型函数语言,请尽早并经常使用类型检查器。如果您使用的是Lisp或Clojure,最好的做法是首先编写函数契约,包括单元测试——这是最大程度的测试驱动开发。你会想要使用任何版本的QuickCheck已经移植到你的平台,在你的情况下,它看起来像ClojureCheck。它是一个非常强大的库,用于构造使用高阶函数的代码随机测试。

老实说,如果你想设计函数式程序,可以看看标准函数库,比如Haskell的Prelude。在FP中,模式通常由高阶过程(对函数进行操作的函数)本身捕获。因此,如果看到了一个模式,通常会创建一个更高阶的函数来捕捉该模式。

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

有一种“程序计算”/“通过计算设计”的风格与Richard Bird教授和牛津大学(英国)的代数编程组有关,我不认为将其视为一种方法论太牵强。

就我个人而言,虽然我喜欢AoP小组的工作,但我自己没有以这种方式实践设计的规程。但这是我的缺点,不是程序计算的缺点之一。

我发现行为驱动开发非常适合在Clojure和SBCL中快速开发代码。与函数式语言一起使用BDD的真正好处是,我倾向于编写比使用过程式语言更精细的单元测试,因为我在将问题分解为更小的功能块方面做得更好。

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

此外,函数式程序确实表现出模块化和其他结构。你的详细设计必须根据该结构中的概念来表达。

好吧,

一般来说,许多函数式编程语言在大学里被用来解决“小玩具问题”已经很长时间了。

由于“状态”的原因,OOP在“并行编程”方面有困难,因此它们现在变得越来越流行。有时函数式风格更适合解决手头的问题,比如谷歌MapReduce。

我敢肯定,当功能人员碰壁(尝试实现超过1.000.000行代码的系统)时,他们中的一些人会提出新的软件工程方法,并使用流行语:-)。他们应该回答这个老问题:如何将系统分成几部分,以便我们可以一次“咬”每一部分?使用功能风格[工作迭代,仪式和进化的方式]。

函数式风格肯定会影响我们的面向对象 风格。我们“保留”了功能系统中的许多概念并加以适应 我们的OOP语言。

但是函数式程序会被用于这样一个大系统吗?它们会成为主流吗?这就是问题所在

没有人能提出现实的方法,而不实施这样一个大系统,弄脏自己的手。 首先你应该把自己的手弄脏,然后再提出解决方案。没有“真正的痛苦和肮脏”的解决方案-建议将是“幻想”
我最近发现了这本书: 功能和反应域建模 < / p >

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

从书的描述:

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