并非所有函数式语言都使用显式类型,但是How To Design Programs一书是学习Scheme/Lisp的优秀书籍/Clojure,严重依赖于“数据描述”,它与类型密切相关。
那么,功能应用程序(即 Lisp 或 Clojure)的系统(基于模型?)设计的方法是什么?
任何基于数据抽象的设计方法都很有效。我碰巧认为,当语言具有显式类型时,这会更容易,但即使没有显式类型,它也能工作。 Barbara Liskov 和 John Guttag 编写的《程序开发中的抽象和规范》是一本关于抽象数据类型设计方法的好书,很容易适应函数式编程,这是第一版。利斯科夫获得图灵奖的部分原因是这项工作。
Lisp 独有的另一种设计方法是确定哪些语言扩展在您正在处理的问题领域中有用,然后使用卫生宏将这些结构添加到您的语言中。 Matthew Flatt 的文章在 Racket 中创建语言是了解此类设计的好地方。该文章可能位于付费墙后面。您还可以通过搜索术语“特定于领域的嵌入式语言”来找到有关此类设计的更多通用材料;对于 Matthew Flatt 所涵盖范围之外的特定建议和示例,我可能会从 Graham 的 On Lisp 或ANSI Common Lisp。
Thank God that the software-engineering people have not yet discovered functional programming. Here are some parallels:
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.
So what is the methodology for a systematic (model-based ?) design of a functional application, i.e. in Lisp or Clojure?
Any design method based on data abstraction works well. I happen to think that this is easier when the language has explicit types, but it works even without. A good book about design methods for abstract data types, which is easily adapted to functional programming, is Abstraction and Specification in Program Development by Barbara Liskov and John Guttag, the first edition. Liskov won the Turing award in part for that work.
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.
What are the common steps, what artifacts do I use?
Common steps:
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.
Personally I find that all the usual good practices from OO development apply in functional programming as well - just with a few minor tweaks to take account of the functional worldview. From a methodology perspective, you don't really need to do anything fundamentally different.
My experience comes from having moved from Java to Clojure in recent years.
Some examples:
Understand your business domain / data model - equally important whether you are going to design an object model or create a functional data structure with nested maps. In some ways, FP can be easier because it encourages you to think about data model separately from functions / processes but you still have to do both.
Service orientation in design - actually works very well from a FP perspective, since a typical service is really just a function with some side effects. I think that the "bottom up" view of software development sometimes espoused in the Lisp world is actually just good service-oriented API design principles in another guise.
Test Driven Development - works well in FP languages, in fact sometimes even better because pure functions lend themselves extremely well to writing clear, repeatable tests without any need for setting up a stateful environment. You might also want to build separate tests to check data integrity (e.g. does this map have all the keys in it that I expect, to balance the fact that in an OO language the class definition would enforce this for you at compile time).
Prototying / iteration - works just as well with FP. You might even be able to prototype live with users if you get very extremely good at building tools / DSL and using them at the REPL.
OO programming tightly couples data with behavior. Functional programming separates the two. So you don't have class diagrams, but you do have data structures, and you particularly have algebraic data types. Those types can be written to very tightly match your domain, including eliminating impossible values by construction.
So there aren't books and books on it, but there is a well established approach to, as the saying goes, make impossible values unrepresentable.
In so doing, you can make a range of choices about representing certain types of data as functions instead, and conversely, representing certain functions as a union of data types instead so that you can get, e.g., serialization, tighter specification, optimization, etc.
Then, given that, you write functions over your adts such that you establish some sort of algebra -- i.e. there are fixed laws which hold for these functions. Some are maybe idempotent -- the same after multiple applications. Some are associative. Some are transitive, etc.
Now you have a domain over which you have functions which compose according to well behaved laws. A simple embedded DSL!
Oh, and given properties, you can of course write automated randomized tests of them (ala QuickCheck).. and that's just the beginning.
Object Oriented design isn't the same thing as software engineering. Software engineering has to do with the entire process of how we go from requirements to a working system, on time and with a low defect rate. Functional programming may be different from OO, but it does not do away with requirements, high level and detailed designs, verification and testing, software metrics, estimation, and all that other "software engineering stuff".
Furthermore, functional programs do exhibit modularity and other structure. Your detailed designs have to be expressed in terms of the concepts in that structure.
One approach is to create an internal DSL within the functional programming language of choice. The "model" then is a set of business rules expressed in the DSL.
I agree more needs to be written on the subject on how to structure large applications that use an FP approach (Plus more needs to be done to document FP-driven UIs)
While this might be considered naive and simplistic, I think "design recipes" (a systematic approach to problem solving applied to programming as advocated by Felleisen et al. in their book HtDP) would be close to what you seem to be looking for.
Functional and Reactive Domain Modeling teaches you how to think of the domain model in terms of pure functions and how to compose them to build larger abstractions. You will start with the basics of functional programming and gradually progress to the advanced concepts and patterns that you need to know to implement complex domain models. The book demonstrates how advanced FP patterns like algebraic data types, typeclass based design, and isolation of side-effects can make your model compose for readability and verifiability.
There is the "program calculation" / "design by calculation" style associated with Prof. Richard Bird and the Algebra of Programming group at Oxford University (UK), I don't think its too far-fetched to consider this a methodology.
Personally while I like the work produced by the AoP group, I don't have the discipline to practice design in this way myself. However that's my shortcoming, and not one of program calculation.
I've found Behavior Driven Development to be a natural fit for rapidly developing code in both Clojure and SBCL. The real upside of leveraging BDD with a functional language is that I tend to write much finer grain unit tests than I usually do when using procedural languages because I do a much better job of decomposing the problem into smaller chunks of functionality.
Honestly if you want design recipes for functional programs, take a look at the standard function libraries such as Haskell's Prelude. In FP, patterns are usually captured by higher order procedures (functions that operate on functions) themselves. So if a pattern is seen, often a higher order function is simply created to capture that pattern.
A good example is fmap. This function takes a function as an argument and applies it to all the "elements" of the second argument. Since it is part of the Functor type class, any instance of a Functor (such as a list, graph, etc...) may be passed as a second argument to this function. It captures the general behavior of applying a function to every element of its second argument.
Generally many Functional Programming Languages are used at universities for a long time for "small toy problems".
They are getting more popular now since OOP has difficulties with "paralel programming" because of "state".And sometime functional style is better for problem at hand like Google MapReduce.
I am sure that, when functioanl guys hit the wall [ try to implement systems bigger than 1.000.000 lines of code], some of them will come with new software-engineering methodologies with buzz words :-). They should answer the old question: How to divide system into pieces so that we can "bite" each pieces one at a time? [ work iterative, inceremental en evolutionary way] using Functional Style.
It is sure that Functional Style will effect our Object Oriented Style.We "still" many concepts from Functional Systems and adapted to our OOP languages.
But will functional programs will be used for such a big systems?Will they become main stream? That is the question.
And Nobody can come with realistic methodology without implementing such a big systems, making his-her hands dirty. First you should make your hands dirty then suggest solution. Solutions-Suggestions without "real pains and dirt" will be "fantasy".
发布评论
评论(13)
感谢上帝,软件工程师还没有发现函数式编程。以下是一些相似之处:
许多面向对象的“设计模式”都被捕获为高阶函数。例如,访问者模式在函数世界中被称为“折叠”(或者,如果您是一位尖刻的理论家,则被称为“变形论”)。在函数式语言中,数据类型主要是树或元组,每种树类型都有与之相关的自然变形。
这些高阶函数通常带有某些编程法则,即“自由定理”。
函数式程序员使用图表的频率比面向对象程序员要少得多。 OO 图中表达的大部分内容都用类型 或“签名”来表达,您应该将其视为“模块类型”。 Haskell 也有“类型类”,这有点像接口类型。
那些使用类型的函数式程序员通常认为“一旦类型正确;代码实际上就可以自行编写。”
并非所有函数式语言都使用显式类型,但是How To Design Programs一书是学习Scheme/Lisp的优秀书籍/Clojure,严重依赖于“数据描述”,它与类型密切相关。
任何基于数据抽象的设计方法都很有效。我碰巧认为,当语言具有显式类型时,这会更容易,但即使没有显式类型,它也能工作。 Barbara Liskov 和 John Guttag 编写的《程序开发中的抽象和规范》是一本关于抽象数据类型设计方法的好书,很容易适应函数式编程,这是第一版。利斯科夫获得图灵奖的部分原因是这项工作。
Lisp 独有的另一种设计方法是确定哪些语言扩展在您正在处理的问题领域中有用,然后使用卫生宏将这些结构添加到您的语言中。 Matthew Flatt 的文章在 Racket 中创建语言是了解此类设计的好地方。该文章可能位于付费墙后面。您还可以通过搜索术语“特定于领域的嵌入式语言”来找到有关此类设计的更多通用材料;对于 Matthew Flatt 所涵盖范围之外的特定建议和示例,我可能会从 Graham 的 On Lisp 或ANSI Common Lisp。
常见步骤:
识别程序中的数据及其操作,并定义表示该数据的抽象数据类型。
识别常见的操作或计算模式,并将它们表达为高阶函数或宏。期望将此步骤作为重构的一部分。
如果您使用类型化函数语言,请尽早并经常使用类型检查器。如果您使用 Lisp 或 Clojure,最佳实践是首先编写函数契约,包括单元测试——最大限度地实现测试驱动开发。您将需要使用已移植到您的平台的任何版本的 QuickCheck,在您的情况下,它看起来像是 ClojureCheck< /a>.它是一个非常强大的库,用于构建使用高阶函数的代码的随机测试。
Thank God that the software-engineering people have not yet discovered functional programming. Here are some parallels:
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.
Any design method based on data abstraction works well. I happen to think that this is easier when the language has explicit types, but it works even without. A good book about design methods for abstract data types, which is easily adapted to functional programming, is Abstraction and Specification in Program Development by Barbara Liskov and John Guttag, the first edition. Liskov won the Turing award in part for that work.
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.
Common steps:
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.
对于 Clojure,我建议回到良好的旧关系建模。 走出 Tarpit< /a> 是一本鼓舞人心的读物。
For Clojure, I recommend going back to good old relational modeling. Out of the Tarpit is an inspirational read.
就我个人而言,我发现面向对象开发的所有常见良好实践也适用于函数式编程 - 只需进行一些小的调整即可考虑到函数式世界观。从方法论的角度来看,您实际上不需要做任何根本不同的事情。
我的经验来自于近年来从 Java 迁移到 Clojure。
一些示例:
了解您的业务领域/数据模型 - 无论您是要设计对象模型还是使用嵌套映射创建功能数据结构,都同样重要。在某些方面,FP 可以更容易,因为它鼓励您将数据模型与功能/流程分开考虑,但您仍然必须两者都做。
设计中的面向服务 - 从 FP 的角度来看实际上效果很好,因为典型的服务实际上只是一个带有一些副作用的函数。我认为 Lisp 世界中有时所拥护的软件开发“自下而上”的观点实际上只是另一种形式的良好的面向服务的 API 设计原则。
测试驱动开发 - 在 FP 语言中效果很好,事实上有时甚至更好,因为纯函数非常适合编写清晰、可重复的测试,而无需设置有状态的环境。您可能还想构建单独的测试来检查数据完整性(例如,此映射是否包含我期望的所有键,以平衡在 OO 语言中类定义将在编译时为您强制执行这一事实)。< /p>
原型/迭代 - 与 FP 一样有效。如果您非常擅长构建工具/DSL 并在 REPL 中使用它们,您甚至可以与用户一起实时制作原型。
Personally I find that all the usual good practices from OO development apply in functional programming as well - just with a few minor tweaks to take account of the functional worldview. From a methodology perspective, you don't really need to do anything fundamentally different.
My experience comes from having moved from Java to Clojure in recent years.
Some examples:
Understand your business domain / data model - equally important whether you are going to design an object model or create a functional data structure with nested maps. In some ways, FP can be easier because it encourages you to think about data model separately from functions / processes but you still have to do both.
Service orientation in design - actually works very well from a FP perspective, since a typical service is really just a function with some side effects. I think that the "bottom up" view of software development sometimes espoused in the Lisp world is actually just good service-oriented API design principles in another guise.
Test Driven Development - works well in FP languages, in fact sometimes even better because pure functions lend themselves extremely well to writing clear, repeatable tests without any need for setting up a stateful environment. You might also want to build separate tests to check data integrity (e.g. does this map have all the keys in it that I expect, to balance the fact that in an OO language the class definition would enforce this for you at compile time).
Prototying / iteration - works just as well with FP. You might even be able to prototype live with users if you get very extremely good at building tools / DSL and using them at the REPL.
面向对象编程将数据与行为紧密耦合。函数式编程将两者分开。所以你没有类图,但你有数据结构,特别是代数数据类型。这些类型可以编写为非常紧密地匹配您的域,包括通过构造消除不可能的值。
因此,没有关于这方面的书籍和书籍,但有一种行之有效的方法,正如俗话所说,使不可能的价值变得不可再现。
这样做时,您可以做出一系列选择,将某些类型的数据表示为函数,反之,将某些函数表示为数据类型的联合,以便您可以获得序列化、更严格的规范、优化等 然后,
鉴于此,您可以在 adt 上编写函数,以便建立某种代数 - 即这些函数有固定的定律。有些可能是幂等的——多次应用后仍然相同。有些是关联的。有些是传递性的,等等。
现在你有了一个域,在这个域上你有根据良好行为法则组成的函数。一个简单的嵌入式 DSL!
哦,给定属性,您当然可以编写它们的自动随机测试(ala QuickCheck)..而这只是开始。
OO programming tightly couples data with behavior. Functional programming separates the two. So you don't have class diagrams, but you do have data structures, and you particularly have algebraic data types. Those types can be written to very tightly match your domain, including eliminating impossible values by construction.
So there aren't books and books on it, but there is a well established approach to, as the saying goes, make impossible values unrepresentable.
In so doing, you can make a range of choices about representing certain types of data as functions instead, and conversely, representing certain functions as a union of data types instead so that you can get, e.g., serialization, tighter specification, optimization, etc.
Then, given that, you write functions over your adts such that you establish some sort of algebra -- i.e. there are fixed laws which hold for these functions. Some are maybe idempotent -- the same after multiple applications. Some are associative. Some are transitive, etc.
Now you have a domain over which you have functions which compose according to well behaved laws. A simple embedded DSL!
Oh, and given properties, you can of course write automated randomized tests of them (ala QuickCheck).. and that's just the beginning.
面向对象设计与软件工程不同。软件工程涉及我们如何按时且低缺陷率从需求到工作系统的整个过程。函数式编程可能与面向对象不同,但它并没有消除需求、高级和详细设计、验证和测试、软件度量、估计以及所有其他“软件工程内容”。
此外,函数式程序确实表现出模块化和其他结构。您的详细设计必须用该结构中的概念来表达。
Object Oriented design isn't the same thing as software engineering. Software engineering has to do with the entire process of how we go from requirements to a working system, on time and with a low defect rate. Functional programming may be different from OO, but it does not do away with requirements, high level and detailed designs, verification and testing, software metrics, estimation, and all that other "software engineering stuff".
Furthermore, functional programs do exhibit modularity and other structure. Your detailed designs have to be expressed in terms of the concepts in that structure.
一种方法是在所选的函数式编程语言中创建内部 DSL。那么“模型”就是用 DSL 表达的一组业务规则。
One approach is to create an internal DSL within the functional programming language of choice. The "model" then is a set of business rules expressed in the DSL.
请参阅我对另一篇文章的回答:
Clojure 如何实现关注点分离?
我同意关于如何构建使用 FP 方法的大型应用程序的主题需要编写更多内容(此外还需要做更多工作来记录 FP 驱动的 UI)
See my answer to another post:
How does Clojure aproach Separation of Concerns?
I agree more needs to be written on the subject on how to structure large applications that use an FP approach (Plus more needs to be done to document FP-driven UIs)
虽然这可能被认为是幼稚和简单化的,但我认为“设计食谱”(一种应用于编程的解决问题的系统方法,如 Felleisen 等人在他们的书中所倡导的HtDP)将接近您似乎正在寻找的内容。
这里有一些链接:
http://www.northeastern.edu/magazine/0301/programming .html
http://citeseerx.ist.psu .edu/viewdoc/summary?doi=10.1.1.86.8371
While this might be considered naive and simplistic, I think "design recipes" (a systematic approach to problem solving applied to programming as advocated by Felleisen et al. in their book HtDP) would be close to what you seem to be looking for.
Here, a few links:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
我最近找到了这本书:
功能性和反应性域建模
我认为完全符合你的问题。
从书中的描述来看:
I've recently found this book:
Functional and Reactive Domain Modeling
I think is perfectly in line with your question.
From the book description:
英国牛津大学的Richard Bird教授和编程代数小组有一种“程序计算”/“计算设计”风格,我认为将其视为一种方法论并不牵强。
就我个人而言,虽然我喜欢 AoP 小组的工作,但我自己没有以这种方式实践设计的纪律。但这是我的缺点,而不是程序计算的缺点。
There is the "program calculation" / "design by calculation" style associated with Prof. Richard Bird and the Algebra of Programming group at Oxford University (UK), I don't think its too far-fetched to consider this a methodology.
Personally while I like the work produced by the AoP group, I don't have the discipline to practice design in this way myself. However that's my shortcoming, and not one of program calculation.
我发现行为驱动开发非常适合在 Clojure 和 SBCL 中快速开发代码。通过函数式语言利用 BDD 的真正好处是,我倾向于编写比通常使用过程语言时更细粒度的单元测试,因为我可以更好地将问题分解为更小的功能块。
I've found Behavior Driven Development to be a natural fit for rapidly developing code in both Clojure and SBCL. The real upside of leveraging BDD with a functional language is that I tend to write much finer grain unit tests than I usually do when using procedural languages because I do a much better job of decomposing the problem into smaller chunks of functionality.
老实说,如果您想要函数式程序的设计秘诀,请查看标准函数库,例如 Haskell 的 Prelude。在 FP 中,模式通常由高阶过程(对函数进行操作的函数)本身捕获。因此,如果看到某种模式,通常会简单地创建一个高阶函数来捕获该模式。
fmap 就是一个很好的例子。该函数接受一个函数作为参数,并将其应用于第二个参数的所有“元素”。由于它是 Functor 类型类的一部分,因此 Functor 的任何实例(例如列表、图形等)都可以作为第二个参数传递给该函数。它捕获将函数应用于其第二个参数的每个元素的一般行为。
Honestly if you want design recipes for functional programs, take a look at the standard function libraries such as Haskell's Prelude. In FP, patterns are usually captured by higher order procedures (functions that operate on functions) themselves. So if a pattern is seen, often a higher order function is simply created to capture that pattern.
A good example is fmap. This function takes a function as an argument and applies it to all the "elements" of the second argument. Since it is part of the Functor type class, any instance of a Functor (such as a list, graph, etc...) may be passed as a second argument to this function. It captures the general behavior of applying a function to every element of its second argument.
嗯,
一般来说,许多函数式编程语言在大学中长期用于“小玩具问题”。
它们现在变得越来越流行,因为 OOP 由于“状态”而在“并行编程”方面遇到困难。有时函数式风格更适合手头的问题,例如 Google MapReduce。
我确信,当功能人员碰壁时[尝试实现超过 1,000,000 行代码的系统],他们中的一些人会使用带有流行语的新软件工程方法:-)。他们应该回答这个老问题:如何将系统分成几个部分,以便我们可以一次“咬住”每个部分? [以迭代、渐进和进化的方式工作]使用函数式风格。
但是函数式程序会用于这么大的系统吗?它们会成为主流吗?这就是问题。
没有人能够在不实施如此大的系统、弄脏他/她的手的情况下提出现实的方法。
首先你应该先把手弄脏,然后提出解决方案。解决方案——没有“真正的痛苦和污垢”的建议将是“幻想”。
Well,
Generally many Functional Programming Languages are used at universities for a long time for "small toy problems".
They are getting more popular now since OOP has difficulties with "paralel programming" because of "state".And sometime functional style is better for problem at hand like Google MapReduce.
I am sure that, when functioanl guys hit the wall [ try to implement systems bigger than 1.000.000 lines of code], some of them will come with new software-engineering methodologies with buzz words :-). They should answer the old question: How to divide system into pieces so that we can "bite" each pieces one at a time? [ work iterative, inceremental en evolutionary way] using Functional Style.
But will functional programs will be used for such a big systems?Will they become main stream? That is the question.
And Nobody can come with realistic methodology without implementing such a big systems, making his-her hands dirty.
First you should make your hands dirty then suggest solution. Solutions-Suggestions without "real pains and dirt" will be "fantasy".