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

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

函数式程序通常是自底向上而不是自顶向下开发的(《论Lisp》,Paul Graham) 函数式程序员使用映射,而oop程序员使用对象/类(《Clojure for Java Programmers》,Rich Hickley谈话)。

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


当前回答

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

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

其他回答

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

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

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

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

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

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

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

对于Clojure,我建议回到良好的旧式关系建模。《走出塔皮》是一本励志读物。

好吧,

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

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

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

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

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

没有人能提出现实的方法,而不实施这样一个大系统,弄脏自己的手。 首先你应该把自己的手弄脏,然后再提出解决方案。解决方案-没有“真正的痛苦和污垢”的建议将是“幻想”。

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

Many OO "design patterns" are captured as higher-order functions. For example, the Visitor pattern is known in the functional world as a "fold" (or if you are a pointy-headed theorist, a "catamorphism"). In functional languages, data types are mostly trees or tuples, and every tree type has a natural catamorphism associated with it. These higher-order functions often come with certain laws of programming, aka "free theorems". Functional programmers use diagrams much less heavily than OO programmers. Much of what is expressed in OO diagrams is instead expressed in types, or in "signatures", which you should think of as "module types". Haskell also has "type classes", which is a bit like an interface type. Those functional programmers who use types generally think that "once you get the types right; the code practically writes itself." Not all functional languages use explicit types, but the How To Design Programs book, an excellent book for learning Scheme/Lisp/Clojure, relies heavily on "data descriptions", which are closely related to types.

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

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

Another design methodology that is unique to Lisp is to decide what language extensions would be useful in the problem domain in which you are working, and then use hygienic macros to add these constructs to your language. A good place to read about this kind of design is Matthew Flatt's article Creating Languages in Racket. The article may be behind a paywall. You can also find more general material on this kind of design by searching for the term "domain-specific embedded language"; for particular advice and examples beyond what Matthew Flatt covers, I would probably start with Graham's On Lisp or perhaps ANSI Common Lisp.

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

常见的步骤:

Identify the data in your program and the operations on it, and define an abstract data type representing this data. Identify common actions or patterns of computation, and express them as higher-order functions or macros. Expect to take this step as part of refactoring. If you're using a typed functional language, use the type checker early and often. If you're using Lisp or Clojure, the best practice is to write function contracts first including unit tests—it's test-driven development to the max. And you will want to use whatever version of QuickCheck has been ported to your platform, which in your case looks like it's called ClojureCheck. It's an extremely powerful library for constructing random tests of code that uses higher-order functions.