序言: 面向对象编程(OOP)与函数式编程(FP)

Closure Notes, 这是我的一本Closure学习笔记.

Code is data.(代码即数据)


面向对象编程(OOP)

面向对象编程(OOP)中最终要的是什么?抽象、封装、集成、多态?实现模式?设计模式? 还有更重要的么?

下面引用两段业内名言:

“如果我们现在回头看一下面向对象这个思想是从哪来的,如果以基于消息传递 机制的 Smalltalk-80 的特性来衡量现在的状态继承和面向对象的使用方式,我 们不禁要问,我们是不是已经走错路了?” ——2010 伦敦 QCon 大会采访

只关注状态,在类和基于映像的语言里缺乏良好的并发模型和消息机制。 —— Dave Thomas 博士

到底什么被忽视了?从这两段话中我们可以看出:

是OOP的并发模型和消息机制被现代OO编程语言忽视了,尤其是Java

在当代 OO 语言中,可变状态让并发编程变得非常复杂,只能依靠悲观锁来进行并发的控制。

至于消息传递机制,大都 OO 语言本身并没有提供有效的机制,而是运用设计模式来达到目 的的,但这又会使编程的过程复杂化,也会在一定程度上影响代码的可读性。

至今,业界已经承认 OOP 并不是万能的。而 OOP 的真正优势在于对现实世界的建模,而不是数据处理。我们应该辩证的看待不同范式的编程语言,死磕一个必然会使思想禁锢,甚至编程灵感尽失。

函数范式编程

在计算机科学短短的发展历史中,技术的主流有时会产生分支,包括实用分支和学术分支。20 世纪 90 年代的 4GL(第四代语言)是一个实用分支,而函数式编程是来自学术界的一个示例。每隔一段时间,都会有一些分支加入主流,函数式编程目前也是这种情况。函数式语言不仅在 JVM 上刚刚崭露头脚(其中两个最有趣的新语言是 Scala 和 Clojure),在 .NET 平台上也是才开始得到应用,在 .NET 平台上,F# 是头等公民。

函数式编程的特点

Functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

It is a declarative programming paradigm, which means programming is done with expressions or declarations instead of statements. In functional code, the output value of a function depends only on the arguments that are input to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) each time. Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behavior of a program, which is one of the key motivations for the development of functional programming. (From Wikipedia, the free encyclopedia)

How to get started with functional programming. My answer: Start by writing pure functions in the programming language you’re currently using. The only input to a pure function is its argument list and the only output is its return value.

函数式编程是种编程典范,它将电脑运算视为函数的计算。函数编程语言最重要的基础是 λ 演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里,函数的计算可随时调用。

函数式编程中最古老的例子莫过于1958年被创造出来的LISP了,透过 LISP,可以用精简的人力。较现代的例子包括Haskell、Clean、Erlang和Miranda等。

“Why Functional Programming Matters”论文作者 John Hughes 说明了模块化是成功编程的关键,而函数编程可以极大地改进模块化。在函数编程中,编程人员有一个天然框架用来开发更小的、更简单的和更一般化的模块, 然后将它们组合在一起。函数编程的一些基本特点包括:

支持闭包和高阶函数
支持惰性计算(lazy evaluation)
使用递归作为控制流程的机制
加强了引用透明性
没有副作用

抽象

Java 缓解了我们与内存管理的交互;函数式编程语言使我们能够用高层次的抽象取代其他核心构建块,并更注重结果而不是步骤。 函数式编程的特点之一是存在强大的抽象,它隐藏了许多日常操作的细节(比如迭代)。使用抽象来处理迭代等任务,使得需要维护的代码变得更少,因此可能出现错误的地方也就更少。

少量数据结构,大量操作

在面向对象的命令式编程语言中,重用的单元是类以及与这些类进行通信的消息,这些信息是在类图中捕获的。该领域的开创性著作是 Design Patterns: Elements of Reusable Object-Oriented Software,至少为每个模式提供一个类图。在 OOP 的世界中,鼓励开发人员创建独特的数据结构,以方法的形式附加特定的操作。

函数式编程语言尝试采用不同的方式来实现重用。它们更喜欢一些关键的数据结构(如列表、集和映射),并且在这些数据结构上采用高度优化的操作。传递数据结构和高阶函数,以便 “插入” 这种机制,针对某一特定用途对其进行定制。

函数级的封装支持在比构建自定义类结构更细的基础级别上进行重用。此方法的优势之一已经体现在 Clojure 中。最近,库中的一些巧妙创新重写了 map 函数,使它可以自动并行化,这意味着所有映射操作都可以受益于没有开发人员干预的性能提升。

最大程度地减少可变状态

函数式编程的目标之一是最大程度地减少可变状态。

优雅融合

“functional in the small, OO in the large”  (John D. Cook)

Using functional programming in the small and object oriented programming in the large. In other words, individual methods might be implemented using a functional programming paradigm, but the code would be bundled into an object for other parts of an application to use. The grand structure would be object oriented, but the small structure would be functional.

三种现代JVM语言Groovy,Scala和Clojure

Java的遗产将是平台,而不是程序设计语言。(Martin Fowler)

由于 Java 语言语法与固有范式存在的一些局限性,用 Java 编写大型应用程序时,代码往往十分臃肿。许多语言如 Groovy、Scala 等都把自身设计为一种可替代 Java 的、能直接编译为 JVM 字节码的语言,而 Clojure 则提供了 Lisp 在 JVM 的实现。 Clojure 经过几年的发展,其社区已经逐渐成熟,有许多活跃的开源项目,足以完成大型应用程序的开发。由 Twitter 开源的著名的分布式并行计算框架 Storm 就是用 Clojure 编写的。 Clojure 提供了对 Java 的互操作调用,对于那些必须在 JVM 上继续开发的项目,Clojure 可以利用 Java 遗留代码。对大多数基于 SSH(Spring Struts Hibernate)的 Java 项目来说,是时候扔掉它们,用 Clojure 以一种全新的模式来进行开发了。

函数式与命令式编程

常见的函数式编程语言有:

Clojure, Common Lisp, Erlang, F#, Haskell, ML, OCaml, Scheme and Scala.

命令式编程注重于单步执行的结构,在许多情况下,它是模仿了早期底层硬件的有益结构。函数式编程则注重将函数作为第一等的结构体,以试图将状态传递与可变性降低到最小。

Groovy在很大程度上是受Java的启发,它在根本上仍然是命令式语言。但从一开始,Groovy就加入了许多函数式命令的特性,并且以后还会加入更多的此类特性。

Scala则弥合了这两种编程范式,它同时支持这两种范式。在更偏向(也更鼓励)函数式编程的同时,Scala依然支持面向对象和命令式编程。因此,为了恰当地使用Scala,就要求团队要受到良好的培训,以确保你不会混用和随意地选择编程范式,在多范式编程语言中,这一直都是一个危险。

Clojure是铁杆的函数式编程语言。它也支持面向对象特性,使得它能够很容易地与其它JVM语言进行交互,它并不试图去弥合这两种范式之间的隔阂。相反,Clojure这种义无反顾的决策使它的设计者所考虑的语句成为很好的工程学实践。这些决策具有深远的影响,它使Clojure能够以开创性的方法去解决Java世界中一些挥之不去的问题(如并发)。

超越Java

Java程序设计语言达到卓越的程度就是,按Bruce Tate在他的Beyond Java一书中的说法,完美风暴:Web应用的兴起,已有Web技术由于种种原因不能适应需求,企业级多层应用开发的兴起,这些因素共同造就了Java的卓越。Tate也指出这场风暴是一系列独一无二的事件,曾经没有其它语言使用相同的途径达到相同的卓越程序。

Java语言已经证明其在功能方面的强大灵活性,但它的语法与固有范式则存在着长期已知的局限性。尽管一些承诺过的变化即将引入到该语言中,但Java语法却不能很容易地支持一些重要的未来语法特性,例如函数式编程中的某些特性。但如果你试图去找到一种语言去替代Java,那么你就找错了。

多语言混合编程

没有一种编程语言能够解决每个问题。

有些语言拥有某些内建的特性,使其能够更好地适应特定的问题。例如,由于Swing十分复杂,开发者们发现很难编写Java中的Swing UI,因为它要求事先声明类型,为UI动作定义烦人的匿名内部类,还有其它的麻烦事儿。使用更适合构建UI的语言,如Groovy中的SwingBuilder工具,去构建Swing应用会美妙得多。

运行在JVM上的程序设计语言大量增多,这大大激发了多语言编程理念.

因为你可以混用编号语言,并可使用最佳匹配的语言,但同时却维护着相同的底层字节码和类库.

例如,SwingBuilder并不是要替代Swing;它只是搭建在已有的Swing API之上。当然,在相当长的时间内,开发者们还是将在JVM之外混合使用编程语言--例如,为特定目的而使用SQL和JavaScript--但在JVM的世界内,混合编程将变得更为流行。ThoughtWorks中的许多项目就合用着多种编程语言,而所有由ThoughtWorks Studios开发的工具则都要使用混合语言。

即便Java仍是你主要的开发语言,学习一下其它语言是如何工作的会让你将它们纳入你的未来战略中。Java仍将是JVM生态系统中的重要组成部分,但最终它更多是作为该平台的汇编语言--或是由于纯粹的性能原因,或是在应对特殊需求时才会用到它。

编程语言的进化

编程语言的历史早于真正意义的计算机的出现。19世纪就有"可编程的"织布机和钢琴弹奏装置出现,它们都是领域特定语言(DSL)的样例。

编程语言发展的编年史

我们一个统计出来256种编程语言,当然,这么多的语言中只有一些是常用的或实用的。

1951 – Regional Assembly Language
1952 – Autocode
1954 – IPL (LISP语言的祖先)
1955 – FLOW-MATIC (COBOL语言的祖先)
1957 – FORTRAN (第一个编译型语言)
1957 – COMTRAN (COBOL语言的祖先)
1958 – LISP
1958 – ALGOL 58
1959 – FACT (COBOL语言的祖先)
1959 – COBOL
1959 – RPG
1962 – APL
1962 – Simula
1962 – SNOBOL
1963 – CPL (C语言的祖先)
1964 – BASIC
1964 – PL/I
1966 – JOSS
1967 – BCPL (C语言的祖先)
1968 – Logo
1969 – B (C语言的祖先)
1970 – Pascal
1970 – Forth
1972 – C
1972 – Smalltalk
1972 – Prolog
1973 – ML
1975 – Scheme
1978 – SQL
1980 – C++ (既有类的C语言,更名于1983年7月)
1983 – Ada
1984 – Common Lisp
1984 – MATLAB
1985 – Eiffel
1986 – Objective-C
1986 – Erlang
1987 – Perl
1988 – Tcl
1988 – Mathematica
1989 – FL
1990 – Haskell
1991 – Python
1991 – Visual Basic
1993 – Ruby
1993 – Lua
1994 – CLOS (ANSI Common Lisp的一部分)
1995 – Java
1995 – Delphi (Object Pascal)
1995 – JavaScript
1995 – PHP
1996 – WebDNA
1997 – Rebol
1999 – D
2000 – ActionScript
2001 – C#
2001 – Visual Basic .NET
2002 – F#
2003 – Groovy
2003 – Scala
2007 – Clojure
2009 – Go
2011 – Dart

编程范式

除语法之外,这些语言之间的最有趣的不同之处就是类型及其内在的编程范式:函数式或命令式。

静态类型 vs. 动态类型

编程语言中的静态类型要求显式的类型声明,例如Java中的int x;声明语句。动态类型语言并不要求在声明时提供类型信息。此处所考虑的语言都是强类型语言,意即程序在赋值之后能够反射出类型。

Java的类型系统广受诟病之处就是其静态类型有太多不便,且又没有提供足够的益处。例如,在当前的有限的类型推导出现之前,Java要求开发者在赋值语句两边要重复地声明类型。Scala的类型比Java的更为静态,但在日常使用中所遇到的不便要少得多,因为它大量使用了类型推导。

所有这些语言都有十分强大的元编程功能,所以更为严苛的类型化可以在事后再添加进来。例如,已有多个分支项目将选择性类型(selective type)引入到Clojure中。但一般认为选择性类型是可选的,它不是类型系统的一部分;它只是一个类型验证系统。


The Clojure Programming Language

引用Clojure之父Rich Hickey的话:

Clojure is a dynamic, general-purpose programming language, combining the approachability and interactive development of a scripting language with an efficient and robust infrastructure for multithreaded programming. Clojure is a compiled language, yet remains completely dynamic – every feature supported by Clojure is supported at runtime. Clojure provides easy access to the Java frameworks, with optional type hints and type inference, to ensure that calls to Java can avoid reflection.

Clojure is a dialect of Lisp, and shares with Lisp the code-as-data philosophy and a powerful macro system. Clojure is predominantly a functional programming language, and features a rich set of immutable, persistent data structures. When mutable state is needed, Clojure offers a software transactional memory system and reactive Agent system that ensure clean, correct, multithreaded designs.

I hope you find Clojure's combination of facilities elegant, powerful, practical and fun to use.

Rich Hickey , author of Clojure and CTO Cognitect

为什么Clojure

By relieving the brain of all unnecessary work, a good notation sets it free to concen- trate on more advanced problems. ( Alfred North Whitehead )

Clojure是一个动态类型的,运行在JVM(JDK5.0以上),并且可以和java代码互操作的函数式语言。这个语言的主要目标之一是使得编写一个有多个线程并发访问数据的程序变得简单。

Clojure的发音和单词closure是一样的。Clojure之父是这样解释Clojure名字来历的

“我想把这就几个元素包含在里面: C (C#), L (Lisp) and J (Java). 所以我想到了 Clojure, 而且从这个名字还能想到closure;它的域名又没有被占用;而且对于搜索引擎来说也是个很不错的关键词,所以就有了它了.”

很快Clojure就会移植到.NET平台上了. ClojureCLR是一个运行在Microsoft的CLR的Clojure实现.

Clojure是Lisp方言,提供了强大的函数式编程的支持。

由于Java语言进化的缓慢,用Java编写大型应用程序时,代码往往十分臃肿,许多语言如Groovy、Scala等都把自身设计为一种可替代Java的,能直接编译为JVM字节码的语言。Clojure则提供了Lisp在JVM的实现。

Clojure提供了对Java的互操作调用,对于那些必须在JVM上继续开发的项目,Clojure可以利用Java遗留代码。对大多数基于SSH(Spring Struts Hibernate)的Java项目来说,是时候扔掉它们,用Clojure以一种全新的模式来进行开发了。

从Java转向Clojure的一些理由:

  • 无障碍调用现有的java包
  • 数据结构简单明了(map/list/vector)
  • function作为一等公民,可以做很多java中不好做或做不到的事情
  • 第三方包通常都很小,而且包中的clj是以源码形式存在,在emacs/idea中查看引用包的源码十分方便
  • 做WEB项目有ring规范,request/response都以map形式呈现; 当然你依然可以使用SpringBoot,SpringMVC这样优秀的框架
  • JAVA中听来、讲来都拗口的设计模式几乎都溶于Clojure自身语言的特性中

例子

Clojure经过几年的发展,其社区已经逐渐成熟,有许多活跃的开源项目,足以完成大型应用程序的开发。由Twitter开源的著名的分布式并行计算框架Storm就是用Clojure编写的。

相关书籍

另外,

Joy of Clojure, 作者: Michael Fogus,Chris Houser 
Clojure Programming, 作者: Chas Emerick,Brian Carper, Christophe Grand

这些书会告诉你一堆理由。

关于作者:

陈光剑,江苏东海人, 号行走江湖一剑客,字之剑。程序员,诗人, 作家。(主页: http://universsky.github.io/ )

results matching ""

    No results matching ""