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

简介:

本节书摘来自异步社区《Clojure编程乐趣》一书中的第1章,第1.1节1.1 Clojure之道,作者 【美】Michael Fogus , Chris Houser,更多章节内容可以访问云栖社区“异步社区”公众号查看

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

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


e3a97603cdb758362b49bcc31336582d10036325

图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写的:


4b7e89cbed63b3c4f9ea7c915e64e88c0bcd6e36

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

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

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

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

表1.1 Clojure中的分离关注点


2aac4f1c45d08b73696a7d297893197de6fbd7fa

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

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

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

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


5571f6c6f402aa1095b3577ceddcd269e6cbb552

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

类似地,数据结构的一致性表现在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译注:本小节的标题是清晰,而这段英文绕口令故意展现出不清晰的效果,这里不做翻译。

相关文章
|
Java PHP 开发工具
编程语言Clojure入门
在众多的编程语言中,不少开发人员熟悉Java、C#、PHP等。但是很早以前,也有一些小众的语言,比如Lisp语言,它是一种适用于符号处理和自动推理的编程语言,内部使用表结构来表达非数值计算。而Clojure语言是在JVM上实现的Lisp风格的语言,语法与Lisp类似,且可以和Java语言进行互操作
1140 0
编程语言Clojure入门
|
1月前
|
算法 程序员 开发工具
代码之禅:高效编程的艺术与实践
【2月更文挑战第22天】 在数字时代的浪潮中,编程已不仅仅是一种技术活,它更是一场思维的舞蹈,一种解决问题的艺术。本文将深入探讨如何通过持续学习、精通算法与数据结构、编写可读性强和模块化良好的代码,以及利用版本控制工具来提升编程效率和质量。我们将一起揭开那些隐藏在键盘敲击背后的智慧,探索那些能够使程序员如同艺术家一般在代码世界中自由创作的技巧和方法。
|
Rust
Rust 语言基础 | 学习语言都应该快速得出印象
Rust 语言基础 | 学习语言都应该快速得出印象
133 0
Rust 语言基础 | 学习语言都应该快速得出印象
|
Shell BI 测试技术
Haskell 编程入门
在过去的几个月里,学习Haskell让我觉得非常快乐,但是入门的过程并没有我原先想象的那么简单。我非常幸运地在一个正确的地方工作,并且因此能够在Facebook参加Bryan O'Sullivan的Haskell课程。在Try Haskell上玩了一段时间后,最终你就会想要在自己的电脑上安装GHC了。
173 0
Haskell 编程入门
|
JavaScript 前端开发
准备充分了嘛就想学函数式编程?(第四部分)
本文讲的是准备充分了嘛就想学函数式编程?(第四部分),想要理解函数式编程,第一步总是最重要,也是最困难的。但是只要有了正确的思维,其实也不是太难。
1026 0
|
JavaScript 前端开发 Java
准备充分了嘛就想学函数式编程?(第五部分)
本文讲的是准备充分了嘛就想学函数式编程?(第五部分),迈出理解函数式编程概念的第一步是最重要的,有时也是最难的一步。但是不一定特别难。只要选对了思考方法就不难。
1278 0