Clojure 哲学

已翻译 100%
参与翻译 (2人) : fbm, 咸鱼
加载中

Simplicity, freedom to focus, empowerment, consistency, and clarity: Nearly every element of the Clojure programming language is designed to promote these goals.

Learning a new language generally requires significant investment of thought and effort, and it is only fair that programmers expect each language they consider learning to justify that investment. Clojure was born out of creator Rich Hickey's desire to avoid many of the complications, both inherent and incidental, of managing state using traditional object-oriented techniques. Thanks to a thoughtful design based in rigorous programming language research, coupled with a fervent look toward practicality, Clojure has blossomed into an important programming language playing an undeniably important role in the current state of the art in language design. On one side of the equation, Clojure utilizes Software Transactional Memory (STM), agents, a clear distinction between identity and value types, arbitrary polymorphism, and functional programming to provide an environment conducive to making sense of state in general, and especially in the face of concurrency. On the other side, Clojure shares a close relationship with the Java Virtual Machine, thus allowing prospective developers to avoid the costs of maintaining yet another infrastructure while leveraging existing libraries.

简单性、专心编程不受打扰(freedom to focus)、给力(empowerment)、一致性和明确性:Closure编程语言中几乎每一个元素的设计思想都是为了促成这些目标的实现。


学习一门新的编程语言往往需要花费大量的心思和精力,只有程序员认为他能够从他想学的语言中得到相应的回报,这种学习才是值得的。使用面向对象技术对状态进行管理时,无论是由于面向对象技术内在的因素还是别的偶然因素,都会带来许多不必要的复杂问题,Clojure正是诞生于其创建者Rich Hickey对避免这些问题所做的种种努力。 由于Closure周到的设计方案基于的是在编程语言方面严谨的研究成果,而且在其设计过程中对实用性有着强烈的愿景,所以,Clojure已经茁壮成长为一门重要的编程语言,它在当今编程语言设计领域扮演着一个不容置疑的重要角色。 从一方面讲,Clojure利用了软件事务内存(Software Transactional Memory,简称STM)、agent、在标示(identity)和数值类型(value type)之间划清界线、随心所欲的多态性(arbitrary polymorphism)以及函数编程(functional programming)等诸多手段,提供了一个有助于弄清楚总体状态的环境,特别是在面对并发机制时它更能发挥这方面的作用。从另外一个方面讲,Clojure同Java虚拟机有着密切的关系,从而使得有望使用Clojure的开发者能够避免在利用现有的代码库时再为维护另外一套不同的基础设施付出额外的代价。

In the grand timeline of programming language history, Clojure is an infant; but its colloquialisms (loosely translated as "best practices" or idioms) are rooted in 50 years of Lisp, as well as 15 years of Java history. (While drawing on the traditions of Lisp and Java, Clojure in many ways stands as a direct challenge to them for change.) Additionally, the enthusiastic community that has exploded since its introduction has cultivated its own set of unique idioms. The idioms of a language help to define succinct representations of more complicated expressions. Although we will certainly cover idiomatic Clojure code, we will also expand into deeper discussions of the "why" of the language itself.

In this article, we discuss the weaknesses in existing languages that Clojure was designed to address, how it provides strength in those areas, and many of the design decisions Clojure embodies. We also look at some of the ways existing languages have influenced Clojure.

在编程语言悠久的编年史中, Clojure 算是一个婴儿; 但它的一些俗语(简单的理解为 "最佳实践" 或者 "习惯用法") 源于拥有50年历史的Lisp语言和15年历史的Java语言。 (在吸收了Lisp 和 Java优秀传统的同时, 在许多方面,Clojure 也象征了一些直接挑战他们的变化。) 另外, 自从它问世以来就建立起来的充满热情的社区,已经发展出属于自己的独一无二的习惯用法集。一种语言的习惯用法有助于将比较复杂的表述 定义成简洁的呈现。 我们肯定会涉及到惯用的 Clojure 代码,但是我们还会更深入地讨论关于语言本身为什么这样实现的原因。

在这篇文章中,我们讨论关于现有编程语言中存在的一些不足,Clojure 正是用来解决这些不足的,在这些领域,它如何弥补了这些不足,以及Clojure体现出的许多设计原则。我们还可以看到一些现有的编程语言对Clojure造成的影响。

The Clojure Way

Let's start slowly.

Clojure is an opinionated language — it doesn't try to cover all paradigms or provide every checklist bullet-point feature. Instead it provides the features needed to solve all kinds of real-world problems the Clojure way. To reap the most benefit from Clojure, you'll want to write your code with the same vision as the language itself. As we walk through the language features, we discuss not just what a feature does, but why it's there and how best to take advantage of it.

But before we get to that, we'll first take a high-level view of some of Clojure's most important philosophical underpinnings. Figure 1 lists some broad goals that Rich Hickey had in mind while designing Clojure and some of the more specific decisions that are built into the language to support these goals.

Clojure philosophy
Figure 1: Broad goals of Clojure showing some of the concepts that underlie the Clojure philosophy and how they intersect.

As the figure illustrates, Clojure's broad goals are formed from a confluence of supporting goals and functionality, which we will touch on in the following subsections.

Clojure之道

让我们慢慢开始吧。

Clojure是一门执着于自己的看法的语言 —— 它并不想包含所有的范型(paradigm),也不想提供一项项的重点特性。相反,它只是以Clojure的方式,提供能够足以解决各种现实问题的所有特性。 为了从Clojure中获得最大的好处,你就应该带着和语言本身相同的愿景来写代码。在逐一讨论Clojure的语言特性时,我们不仅仅会给出每个特性是做什么用的,而且还会讨论为什么会有这样的特性以及特性最好的使用方式是什么。

但在进行这些讨论之前,我们先从一个比较高的层层看看Clojure背后最重要一些理念。图1列出的是Rich Hickey在设计Clojure时心里所想的总体目标以及Clojure所包含的能够支持这些目标得以实现的设计决策。

Clojure philosophy
图1: Clojure的总体目标,给出了Clojure背后的理念中所包含的一些概念以及它们之间的交叉关系。

如图所示,Clojure的总体目标是由若干相互支撑的目标和功能组成的,在下面的几个小节中我们将对它们进行一一讨论。

Simplicity

It's hard to write simple solutions to complex problems. But every experienced programmer has also stumbled on areas where we've made things more complex than necessary, what you might call incidental complexity as opposed to complexity that's essential to the task at hand (see "Out of the Tar Pit" for details on the concept). Clojure strives to let you tackle complex problems involving a wide variety of data requirements, multiple concurrent threads, independently developed libraries, and so on without adding incidental complexity. It also provides tools reducing what at first glance may seem like essential complexity. The resulting set of features may not always seem simple, especially when they're still unfamiliar, but we think you'll come to see how much complexity Clojure helps strip away.

One example of incidental complexity is the tendency of modern object-oriented languages to require that every piece of runnable code be packaged in layers of class definitions, inheritance, and type declarations. Clojure cuts through all this by championing the pure function, which takes a few arguments and produces a return value based solely on those arguments. An enormous amount of Clojure is built from such functions, and most applications can be too, which means that there's less to think about when trying to solve the problem at hand.

简单性

要给复杂的问题写出简单的答案可不容易。但每个有经验的程序员都曾遇到过将事情搞到没有必要的那种更加复杂程度的情况,为了完成手头的任务,必不可少要处理一些复杂情况,但前面说的这种没有必要的复杂情况与之不同,可以将其称为次生复杂性(incidental complexity)(这个概念的详细情况可以参见"Out of the Tar Pit" )。Clojure致力于在不增加次生复杂性的前提下就可以让你解决涉及大范围的数据需求(data requirement)、多并发线程以及相互独立开发的代码库等等方面的复杂问题。它还提供了一些工具,可以用来减少乍看起来象是具有必不可少的复杂性的问题。最终的特性集看起来并不总是那么简单,特别是在你还不熟悉它们的时候更是如此,但我们认为,你慢慢就会发现Clojure能够帮你剥离多少复杂性。

举个次生复杂性的例子,现代的面向对象的语言趋向于要求每一段可执行的代码都要以类定义的层次、继承和类型定义的形式进行打包。 Clojure通过对纯函数(pure function)的支持摒弃了这一套陈规。纯函数只接受一些参数并且会仅仅基于这些参数产生一个返回值。 大量的Clojure程序都是由这样的函数构成的,而且绝大多数应用程序都可以做成这样式的,也就是说,在试图解决手头的问题时,需要考虑的因素会比较少。

Freedom to Focus

Writing code is often a constant struggle against distraction, and every time a language requires you to think about syntax, operator precedence, or inheritance hierarchies, it exacerbates the problem. Clojure tries to stay out of your way by keeping things as simple as possible, not requiring you to go through a compile-and-run cycle to explore an idea, not requiring type declarations, and so on. It also gives you tools to mold the language itself so that the vocabulary and grammar available to you fit as well as possible to your problem domain — Clojure is expressive. It packs a punch, allowing you to perform highly complicated tasks succinctly without sacrificing comprehensibility.

不受打扰专心编程(Freedom to Focus)

编写代码的过程往往就是一个不断同令人分心的东西做斗争的过程,每当一种语言迫使你不得不考虑语法、操作符的优先级或者继承关系的层次结构时,它都是在添乱。Clojure努力让这一切保持到最简单的程度,从而不会称为你的绊脚石,不会在你象要探索一个基本想法时还需要进行先编译再运行的这种循环动作。它还向你提供了一些用于改造Closure本身的工具,这样你就可以创造出最适合与你的问题域(problem domain)的词汇和语法了 —— Clojure属于表示性(expressive)语言。它非常强大,能够让你以非常简洁的方式完成高度复杂的任务,同时还不会损失其可理解性。

One key to delivering this freedom is a commitment to dynamic systems. Almost everything defined in a Clojure program can be redefined, even while the program is running: functions, multimethods, types, type hierarchies, and even Java method implementations. Though redefining things on the fly might be scary on a production system, it opens a world of amazing possibilities in how you think about writing programs. It allows for more experimentation and exploration of unfamiliar APIs, and it adds an element of fun that can sometimes be impeded by more static languages and long compilation cycles.

But Clojure's not just about having fun. The fun is a by-product of giving programmers the power to be more productive than they ever thought imaginable.

能够实现这种不受打扰专心编程的一个关键在于格守动态系统(dynamic system)的信条。在Clojure程序中定义的几乎所有东西都可以再次进行重新定义,即使是在程序运行时也没有问题:函数、多重方法(multimethod)、类型以及类型的层次结构甚至Java的方法实现都可以进行重新定义。虽然在生产环境中(a production system)让程序一边运行一边进行重定义可能显得有点可怕,但这么做却在编写程序方面为你打开了一个可以实现各种令人惊叹的可能性的世界。用它可以对不熟悉的API进行更多的实验和探索,并为之增添一丝乐趣,而相比之下,这些实验和探索有时会掣肘于更加静态化的语言以及长时间的编译周期。

但是,Clojure可不仅仅是用来寻找乐趣的。其中的乐趣只是Clojure在赋予程序员以前不敢想象的更高的编程效率时,所带来的副产品而已。

Empowerment

Some programming languages have been created primarily to demonstrate some nugget of academia or to explore certain theories of computation. Clojure is not one of these. Rich Hickey has said on numerous occasions that Clojure has value to the degree that it lets you build interesting and useful applications.

To serve this goal, Clojure strives to be practical — a tool for getting the job done. If a decision about some design point in Clojure had to weigh the trade-offs between the practical solution and a clever, fancy, or theoretically pure solution, usually the practical solution won out. Clojure could try to shield you from Java by inserting a comprehensive API between the programmer and the libraries, but this could make the use of third-party Java libraries more clumsy. So Clojure went the other way: direct, wrapper-free, compiles-to-the-same-bytecode access to Java classes and methods. Clojure strings are Java strings; Clojure function calls are Java method calls — it's simple, direct, and practical.

给力(Empowerment)

有些编程语言之所以会诞生,要么只是为了展示学术界所珍视的某些研究成果,要么就是用来探索某些计算理论的。Clojure可不属于这类语言。Rich Hickey曾在很多场合下说过,Clojure 的价值在于,你用Clojure可以编写出有意思而且也很有用的应用程序。

为了实现该目标,Clojure力求实用 —— 它要成为能够帮助人们完成任务的一个工具。在Clojure的设计过程中,要是需要在一个实用的方案和一个灵巧、花哨或者是基于纯理论的解决方案之间做出权衡选择时,往往实用方案都会胜出。Clojure本可以在程序员和代码库间插入一个无所不包的API,从而将程序员同Java隔离开来,但这么做的话,如果想用第三方Java库就会相当不便。因此,Clojure反其道而行之:它可以直接编译为同普通Java类以及方法完全相同的字节码,中间无需任何封装形式。Clojure中的字符串就是Java字符串;Clojure的函数调用就是Java的方法调用;这一切都是那么简单、直接和实用。

The decision to use the Java Virtual Machine (JVM) itself is a clear example of this practicality. The JVM has some technical weaknesses such as startup time, memory usage, and lack of tail-call optimization (TCO). But it's also an amazingly practical platform — it's mature, fast, and widely deployed. It supports a variety of hardware and operating systems and has a staggering number of libraries and support tools available, all of which Clojure can take advantage of because of this supremely practical decision.

With direct method calls,proxy,gen-class,gen-interface,reify,definterface,deftype, anddefrecord, Clojure works hard to provide a bevy of interoperability options, all in the name of helping you get your job done. Practicality is important to Clojure, but many other languages are practical as well. You'll start to see some ways that Clojure really sets itself apart by looking at how it avoids muddles.

让Clojure直接使用Java虚拟机就是这种实用性方面一个非常明显的例子。JVM在技术方面存在一些不足之处,比如在启动时间、内存使用、缺乏尾递归调用优化技术(tail-call optimization,简称TCO)等等方面。但它仍不失为一种非常可行的平台 —— 它成熟、快速而且已得到广泛的部署。JVM支持大量不同的硬件平台以及操作系统,它拥有数量惊人的代码库以及辅助工具。正是由于Closure这个以实用为上的设计决策使得,所有这一切Clojure都可以直接加以利用。

Closure采用了直接方法调用、proxy、gen-class、gen-interface、reify、definterface、 deftype以及defrecord等等手段,都是致力于提供大量的实现互操作性的选项,其实都是为了帮你完成手头的任务。虽然实用性对Clojure来说非常重要,但是许多其它的语言也很实用。下文你将通过查看Clojure是如何避免添乱的,从而领会Clojure是如何真正成为一门鹤立鸡群的语言的。

Clarity

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. — Dr. Seuss

Consider what might be described as a simple snippet of code in a language like Python:

x=[5]
process(x)
x[0]=x[0]+1
After executing this code, what's the value ofx? If you assumeprocessdoesn't change the contents ofxat all, it should be[6], right? But how can you make that assumption? Without knowing exactly whatprocessdoes, and whatever function it calls does, and so on, you can't be sure at all.

明确性(Clarity)

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. — Dr. Seuss (译者注:这是一段英文绕口令,大致意思是:甲壳虫和甲壳虫在一个水坑里噼里啪啦打了起来,而且这个水坑还是个瓶子里的水坑,所以他们就把这种装了滋滋乱叫的甲壳虫的瓶子叫做噼里啪啦乱作一团的水坑瓶。。。)

请看下面这段可以说是非常简单的一段类似Python的代码:

x=[5]
process(x)
x[0]=x[0]+1
这段代码执行结束后,x的值是多少?如果你process并不会修改x的值的话,就应该是[6],对吧?可是,你怎么能做出这样的假设呢?在不准确乱叫process做了什么以及它还调用了哪些函数的情况下,你根本就无法确定x的值到底是多少。

Even if you're sureprocessdoesn't change the contents ofx, add multithreading and now you have another whole set of concerns. What if some other thread changesxbetween the first and third lines? Worse yet, what if something is settingxat the moment the third line is doing its assignment — are you sure your platform guarantees an atomic write to that variable, or is it possible that the value will be a corrupted mix of multiple writes? We could continue this thought exercise in hopes of gaining some clarity, but the end result would be the same — what you have ends up not being clear at all, but the opposite: a muddle.

Clojure strives for code clarity by providing tools to ward off several different kinds of muddles. For the one just described, it provides immutable locals and persistent collections, which together eliminate most of the single- and multithreaded issues all at once.

即使你可以确信process不会改变x的值,加上多线程后你要考虑的因素就又多了一重。要是另外一个线程在第一行和第三行代码之间对x的值进行了改变会出现什么情况?让情况变得更加糟糕的还有,要是在第三行的赋值操作过程中有线程对x的值进行设定的话,你能确保你所在的平台能够保证修改x的操作的原子性吗?要么x的值最终会是多个写入操作造成了乱数据?为了搞清除所有情况,我们可以不断继续这种思维练习,但最终结果毫无二致 —— 最后你根本就搞不清楚所有情况,最终结果却恰恰相反:乱作一团。

Clojure致力于保持代码的明确性,它提供了可以用来避免多种混乱情况的工具。对于上一段所述的问题,Clojure提供了不可变的局部变量(immutable local)以及持久性的集合数据类型(persistent collection),这二者可以一劳永逸地排除绝大多数由单线程和多线程所引起各种问题。

返回顶部
顶部