《Clojure编程乐趣》—— 第1章,第1.1节Clojure之道

1.1 Clojure之道
Clojure编程乐趣我们会慢些起步。
Clojure是一门观点鲜明的语言,它并不打算涵盖所有编程范式,也不准备提供清单列出每个重要特性。相反,它只提供以 Clojure 之道解决各种真实问题所需的特性。要从 Clojure中获得最大收益,我们就要写出遵循语言自身愿景的代码。在本书中,我们会依次介绍语言特性,但我们想说的并不只是一个特性做了些什么,更重要的是,为什么会有这样的特性,以及如何利用好这样的特性。

但是,开始之前,我们先来从宏观上了解一下Clojure最重要的哲学基础。图1.1列出了Rich Hickey设计Clojure时头脑中一些大致的目标,以及为了支持这些目标而内建在语言中的一些更具体的决策。
如图1.1所示,Clojure的总目标由一些支持目标和功能综合而成,稍后几节,我们会逐一谈及。

图1.1 Clojure的大致目标:本图展示了构成Clojure哲学的一些概念,以及这些概念之间的交互

1.1.1 简单
复杂问题很难有一个简单的解决方案。但是,如果把事情搞得不必要的复杂,即便是有经验的程序员也会栽倒,这就是“偶然复杂性”,与其相对的是任务的本质复杂性(Moseley 2006)。Clojure致力于帮我们解决各种复杂问题,而不引入偶然复杂性,比如,各种数据需求、多并发线程、独立开发的程序库等。它还提供了一些工具,减少了一些初看起来像本质复杂性的东西。如此一来,最终的特性集合或许看起来并不简单,尤其在我们对这些特性还不甚熟悉时,但随着通读本书,我们认为,你会逐渐体会到Clojure去除了多少的复杂性。

偶然复杂性有一个例子,就是现代面向对象程序设计语言的一个发展趋势,即它要将所有可运行代码打包在类定义、继承和类型声明这样的层次里。Clojure通过支持“纯函数”去除了所有这些东西,所谓纯函数就是传入几个实参,然后,只根据这些实参产生一个返回值。Clojure很大一部分就是构建在这样的函数基础上的,绝大多数应用也可以如此,这意味着,尝试解决手头问题时,需要考虑的东西会更少。

1.1.2 专注
写代码总是要和干扰做斗争,每当语言让我们思考语法、运算符优先级、继承层次结构时,只会让干扰增多。Clojure尽力让一切保持尽可能简单,无需为探索一个想法经历“编译—运行”的循环,无需类型声明,等等。它还提供了一些工具,让我们可以改造语言,使词汇和文法能够更好地适应问题领域,因此,Clojure极具表现力。这种做法影响极大,可以在不牺牲可理解性的前提下,很好地完成一些极其复杂的任务。

之所以能够保持专注,关键一点在于恪守对动态系统的承诺。Clojure程序中定义的几乎所有一切都是可以重新定义的,即便程序尚在运行:函数、多重方法、类型、类型层次结构,甚至Java的方法实现。动态重定义这些东西貌似很可怕,尤其是在生产系统上,但它却为思考如何编写程序打开了另一种奇妙的可能性。我们可以对不熟悉的API进行更多的实验和探索,这是一种乐趣,而这种乐趣却常常为更静态的语言、漫长的编译周期所阻碍。

但是,Clojure并不只有乐趣。乐趣只是一种副产品,更重要的是,它可以让程序员有能力获得超乎想象的高效。

1.1.3 实用
某些程序设计语言生来只为展示学术成果,或是探索某种计算理论。Clojure不在此列。Rich Hickey曾在很多场合说过,在构建有趣且有用的应用方面,Clojure是很有价值的。

为达此目标,Clojure努力做到务实 — 一种用于完成工作的工具。在Clojure里,如果某一设计决策要在实用和聪明、花哨或是纯理论的解决方案进行权衡,胜者往往是那些实用的解决方案。Clojure曾试图让我们远离Java,但这样做要在程序员和程序库之间插入大量API,这种做法可能会让第三方Java程序库很难用。所以,Clojure选择了另一条路:不做封装、编译成相同的字节码,能够直接访问Java的类和方法。Clojure字符串就是Java字符串;Clojure函数调用就是Java方法调用。这样做简单、直接、务实。

使用Java虚拟机(JVM)这个决策本身就是一个务实的做法。JVM存在某些技术上的缺陷,诸如启动时间、内存使用、缺乏尾递归优化(tail-call optimization,TCO)1。但是,它也是一个惊人的务实平台—成熟、快速、部署广泛。其支持各种硬件和操作系统,拥有数量众多的程序库,以及支持工具,由于这个极尽务实的决策,所有这一切都可以为Clojure所用。

除了直接的方法调用外,Clojure还有proxy、gen-class、gen-interface(参见第10章)、reify、definterface、deftype和defrecord(参见9.3节),为互操作性提供了许多选择,所有这些都是为了完成工作。务实对Clojure很重要,当然,许多其他语言也同样务实。我们后面会看到Clojure摆脱混乱的一些做法,正是这些地方让它显得与众不同。

1.1.4 清晰

When beetles battle beetles in a puddle paddle battle and the beetle battle puddle is a puddle in a bottle they call this a tweetle beetle bottle puddle paddle battle muddle. 2

—Dr. Seuss

下面有一段简单的代码,可能是用Python写的:

执行这段代码之后,x的值是什么呢?如果假设process没有改变x的内容,那就应该是[6],对吧?但是,怎样才能做这样的假设呢?如果不了解process做了些什么,调用了怎样的函数等,我们根本无法确认。

就算process不会改变x的值,这时,再加入多线程,我们还是会有一大堆顾虑。如果在第一行和第三行之间,另一个线程改变了x会怎么样?还有更糟糕的,如果在第三行做赋值时,某个东西设置了x,那又该如何?你能保证你的平台写变量是原子操作吗?或者,是不是最终的值可能是多个写操作混杂的结果?我们可以抱着获得某种清晰的想法,将这个思维训练无休止地进行下去,但结果是一样的—我们根本无法得到清晰,只会适得其反:混乱。

Clojure为代码的清晰做着努力,提供了一些工具规避几种不同的混乱。就刚才描述的那种情况而言,采用它所提供的不变局部量和持久化集合,便可一并消除了单线程和多线程的大部分问题。

当我们所用的语言将不相关的行为合在一个构造里时,我们不难发现,自己已深陷多种泥潭。Clojure通过分离关注点让我们保持警醒,应对这样的情况。一旦事物得到分离,思路就会清晰许多,只在必要时重新组合。从某种程度上说,这样的做法对某些特定问题非常有用。表1.1将某些语言把概念混杂在一起的常规方式,同Clojure类似概念分离的做法进行了对比,本书稍后会对Clojure的做法进行更详尽的解释。

表1.1 Clojure中的分离关注点

有时,很难在脑子里将这些概念区分开来,但如果能做到的话,就会非常清晰了,为了这种强大和灵活,我们值得努力一试。我们有那么多不同的概念要处理,以一致的方式表现代码和数据就显得很重要了。

1.1.5 一致
Clojure在两个具体的方面提供了一致性:语法和数据结构。

语法一致性指的是,相关的概念在形式上是类似的。有个简洁有力的例子,for和doseq这两个宏之间的语法是一样的。

它们做的事情不尽相同—for返回的是一个惰性seq,而doseq只是为了产生副作用—但二者支持相同的迷你语言(mini-language):嵌套迭代、解构、:when和:while卫语句。比较下面这个例子就不难看出相似性:

这种相似的价值在于,只要学习一种基本语法即可应对两种情况,必要时,在两种用法间切换也会容易许多。

类似地,数据结构的一致性表现在Clojure持久化集合类型的精心设计上,它为各个类型提供了尽可能相似的接口,并尽可能广泛地去使用这些接口。这种做法实际上是Lisp经典的“代码即数据”哲学的扩展。Clojure数据结构不只可以持有一大堆应用的数据,还可以持有应用自身的一些表达式元素。它们可以描述对form的解构,还可以为各种内建函数提供命名选项(named options)。其他面向对象语言可能会鼓励应用定义多个彼此不相容的类,以持有不同类型的应用数据,而Clojure则鼓励使用行为上类似于map的对象。

这样做的好处在于,同样一套处理Clojure数据结构的函数可以用于下列所有情形:大规模数据存储、应用代码和应用数据对象。用into可以构建任意类型,用seq可以获取一个用于遍历的惰性seq,用filter可以选择满足特定条件的元素,等等。一旦习惯了所有这些丰富且随处可用的函数,用Java或C++处理应用中的Person或Address这样的类就会让人觉得处处掣肘。

简单、专注、实用、一致和清晰。
Clojure程序设计语言里几乎所有元素都是为了提振这些目标。编写Clojure代码处理真实问题时,请将“简单、实用、专注”等方面推向极致,将此铭记于心,我们相信你会发现,Clojure就是你到达成功彼岸所需的工具。

1如果你不了解尾递归优化是什么,请不必担心。如果你知道TCO是什么,也不必担心JVM在这方面的欠缺会成为Lisp或是像Clojure这样函数式语言的致命缺陷。我们会在7.3节讨论所有这些顾虑。在此之前,放松就好。
2译注:本小节的标题是清晰,而这段英文绕口令故意展现出不清晰的效果,这里不做翻译。

时间: 2024-12-30 21:50:03

《Clojure编程乐趣》—— 第1章,第1.1节Clojure之道的相关文章

《Clojure编程乐趣》—— 第1章,第1.4节Clojure为何不是面向对象的

1.4 Clojure为何不是面向对象的 Clojure编程乐趣 优雅同熟悉正交. -Rich Hickey Clojure的出现源自一种无奈,很大程度要归因于并发编程复杂性以及面向对象程序设计在这方面的无能为力.本节将会探索这些缺陷,了解Clojure之所以是函数式而非面向对象的根因. 1.4.1 定义术语 开始之前,先定义术语1. 第一个要定义的重要术语是时间(time).简单说来,时间是指事件发生的相对时刻.有了时间,同实体关联在一起的属性-无论是静态还是动态,单数的还是组合的-会形成一种

《Clojure编程乐趣》—— 第1章,第1.5节小结

1.5 小结 Clojure编程乐趣 我们在本章讨论了很多概念性的东西,但这是必需的,定义了本书余下部分用到的术语.类似地,要进行后续讨论,理解Clojure的基础是很重要的.如果你能从之前的章节了解这些内容,甚至内化为自身的一部分,那么恭喜你:你拥有了继续阅读本书的坚实基础.但如果你尚不确定自己是否了解Clojure,那也OK-我们可以理解,一次消化所有这些内容有点多.随着我们逐渐讲述Clojure的故事,这些东西慢慢就能理解了.如果有函数式编程的背景,之前章节的一些讨论,你很可能会感到很熟悉

《Clojure编程乐趣》—— 第1章,第1.2节为何(又一种)Lisp

1.2 为何(又一种)Lisp Clojure编程乐趣 一套好的概念可以将大脑从无用功中解放出来,专注于更高级的问题. -Alfred North Whitehead 去到任何一个开源项目托管站点,搜索"Lisp interpreter"(Lisp解析器).这个貌似平淡无奇的搜索,可能会带给我们一个堆积如山1**的结果.事实上,计算机科学的历史中堆积着大量废弃的Lisp实现(Fogus 2009).诸多初衷良好的Lisp来了又走,一路遭到无数嘲笑,但到了明天,搜索结果依然会无限制增长.

《Clojure编程乐趣》—— 第1章,第1.3节函数式编程

1.3 函数式编程 Clojure编程乐趣 快点回答,函数式编程是什么意思?错! 别太泄气,其实,我们也不知道确切的答案是什么.函数式编程只是诸多定义模糊的计算机术语1中的一个.如果找100个程序员问它的定义,我们会得到100个不同的答案.确实,某些答案是类似的,但如同雪花一般,没有两个答案是完全一样的.要进一步搅混水的话,让计算机科学的专家们单独给出定义,我们可能会发现,某些答案甚至是彼此矛盾的.同样,任何一个函数式编程定义的基本结构都可能会不同,这完全取决于回答问题的人喜欢用哪种语言写程序:

《Clojure编程乐趣》——导读

目 录 第1部分 基础 第1章 Clojure哲学 1.1 Clojure之道1.2 为何(又一种)Lisp1.3 函数式编程 1.4 Clojure为何不是面向对象的1.5 小结 第2章 Clojure疾风式教程 第2章 Clojure疾风式教程 第3章 小试牛刀 第2部分 数据类型 第4章 标量 第5章 组合数据类型 第3部分 函数式编程 第6章 惰性与不变性 第7章 函数式编程 第4部分 大规模设计 第8章 宏 第9章 组合数据与代码 第10章 Java.next 第11章 变化 第5部分

《Clojure程序设计》——第1章,第1.2节Clojure编程快速入门

1.2 Clojure编程快速入门 Clojure程序设计 要运行Clojure及本书的示例代码,你需要两件东西. Java运行时.请下载1并安装Java 5或是更高版本.Java 6具有显著的性能提升和更好的异常报告,如果可能就尽量选它吧. Leiningen2.Leiningen是一个用于管理依赖项的工具,并且可以基于你的代码启动各种任务.在Clojure世界中,它是处理这项工作最常用的工具了. 你将会使用Leiningen来安装Clojure和本书所有示例代码的依赖项.如果你已经安装了Le

java编程思想-java编程四线第二十一章 线程SynchronizationComparisons.java有错误

问题描述 java编程四线第二十一章 线程SynchronizationComparisons.java有错误 //BaseLine 和AtomicTest 是线程不安全的 ,求解答 //: concurrency/SynchronizationComparisons.java// Comparing the performance of explicit Locks// and Atomics versus the synchronized keyword.import java.util.c

《树莓派Python编程入门与实战》——第一部分 树莓派编程环境 第1章 配置树莓派 1.1 树莓派是什么

第一部分 树莓派编程环境 第1章 配置树莓派 第2章 认识Raspbian linux发行版 第3章 搭建编程环境 第1章 配置树莓派 在本章中,你将学习如下内容. 树莓派是什么 如何获得一个树莓派 你的树莓派可能需要的一些外设 如何让树莓派工作 如何排除树莓派的故障 本章主要介绍树莓派:它是什么,它的历史,以及为什么你需要学习用Python在树莓派上进行编程.最后,你将了解到一些树莓派的外设以及将这些外设与树莓派组装好并运行起来的方法. 1.1 树莓派是什么 树莓派是一个非常廉价的.只有手掌大

《树莓派Python编程入门与实战(第2版)》——第一部分 树莓派编程环境 第1章 配置树莓派 1.1 获取树莓派

第一部分 树莓派编程环境 第1章 配置树莓派 第2章 认识Raspbian Linux发行版 第3章 搭建编程环境 第1章 配置树莓派 本章主要内容包括: 树莓派是什么 如何获得一个树莓派 你的树莓派可能需要的一些外围设备 如何让树莓派工作 如何排除树莓派的故障 本章主要介绍树莓派:它是什么,它的历史,以及为什么需要学习用Python在树莓派上编程.最后,你将了解到一些树莓派的外围设备以及将这些外围设备与树莓派组装好并运行起来的方法. 1.1 获取树莓派 树莓派是一个非常便宜的.只有手掌大小的完