Introduction to Clojure – Clojure简介

最后修改: 2019年 1月 27日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Clojure is a functional programming language that runs entirely on the Java Virtual Machine, in a similar way to Scala and Kotlin. Clojure is considered to be a Lisp derivative and will be familiar to anyone who has experience with other Lisp languages.

Clojure是一种完全在Java虚拟机上运行的函数式编程语言,其方式与Scala和Kotlin类似。Clojure被认为是Lisp的衍生物,任何有其他Lisp语言经验的人都会熟悉它。

This tutorial gives an introduction to the Clojure language, introducing how to get started with it and some of the key concepts to how it works.

本教程对Clojure语言进行了介绍,介绍了如何开始使用它以及它如何工作的一些关键概念。

2. Installing Clojure

2.安装Clojure

Clojure is available as installers and convenience scripts for use on Linux and macOS. Unfortunately, at this stage, Windows doesn’t have such an installer.

Clojure可作为安装程序和便利脚本用于Linux和macOS。不幸的是,在这个阶段,Windows并没有这样的安装程序。

However, the Linux scripts might work in something such as Cygwin or Windows Bash. There is also an online service that can be used to test out the language, and older versions have a standalone version that can be used.

然而,Linux的脚本可能在诸如Cygwin或Windows Bash中工作。还有一个在线服务,可以用来测试该语言,旧版本有一个独立的版本可以使用。

2.1. Standalone Download

2.1.单机下载

The standalone JAR file can be downloaded from Maven Central. Unfortunately, versions newer than 1.8.0 no longer work this way easily due to the JAR file having been split into smaller modules.

独立的JAR文件可以从Maven中心>下载。遗憾的是,由于JAR文件被分割成小模块,1.8.0以上的版本不再能轻易使用这种方式。

Once this JAR file is downloaded, we can use it as an interactive REPL simply by treating it as an executable JAR:

一旦下载了这个JAR文件,我们就可以把它当作一个可执行的JAR文件来使用,作为一个交互式REPL。

$ java -jar clojure-1.8.0.jar
Clojure 1.8.0
user=>

2.2. Web Interface to REPL

2.2.REPL的网络接口

A web interface to the Clojure REPL is available at https://repl.it/languages/clojure for us to try without needing to download anything. Currently, this only supports Clojure 1.8.0 and not the newer releases.

https://repl.it/languages/clojure上有一个Clojure REPL的网络接口,供我们试用,而不需要下载任何东西。目前,这只支持Clojure 1.8.0,不支持较新的版本。

2.3. Installer on MacOS

2.3.MacOS上的安装程序

If you use macOS and have Homebrew installed, then the latest release of Clojure can be installed easily:

如果你使用macOS并安装了Homebrew,那么最新版本的Clojure就可以轻松安装。

$ brew install clojure

This will support the latest version of Clojure – 1.10.0 at time of writing. Once installed, we can load the REPL simply by using the clojure or clj commands:

这将支持Clojure的最新版本–在撰写本文时为1.10.0。安装后,我们可以通过使用clojureclj命令加载REPL。

$ clj
Clojure 1.10.0
user=>

2.4. Installer on Linux

2.4.Linux上的安装程序

A self-installing shell script is available for us to install the tools on Linux:

一个自我安装的shell脚本可供我们在Linux上安装这些工具。

$ curl -O https://download.clojure.org/install/linux-install-1.10.0.411.sh
$ chmod +x linux-install-1.10.0.411.sh
$ sudo ./linux-install-1.10.0.411.sh

As with the macOS installer, these will be available for the most recent releases of Clojure and can be executed using the clojure or clj commands.

与macOS的安装程序一样,这些将适用于Clojure的最新版本,可以使用clojureclj命令执行。

3. Introduction to the Clojure REPL

3.Clojure REPL介绍

All of the above options give us access to the Clojure REPL. This is the direct Clojure equivalent of the JShell tool for Java 9 and above and allows us to enter Clojure code and see the result immediately directly. This is a fantastic way to experiment and discover how certain language features work.

上述所有的选项都可以让我们访问Clojure REPL。这相当于Java 9及以上版本的JShell工具的直接Clojure功能,允许我们输入Clojure代码并直接看到结果。这是一个实验和发现某些语言功能如何工作的绝佳方式。

Once the REPL is loaded, we’ll have a prompt at which any standard Clojure code can be entered and immediately executed. This includes simple Clojure constructs, as well as interaction with other Java libraries – though they need to be available on the classpath to be loaded.

一旦REPL被加载,我们将有一个提示,任何标准的Clojure代码都可以被输入并立即执行。这包括简单的Clojure结构,以及与其他Java库的交互–尽管它们需要在classpath上可用,才能被加载。

The prompt of the REPL is an indication of the current namespace we are working in. For the majority of our work, this is the user namespace, and so the prompt will be:

REPL的提示是对我们当前工作的命名空间的指示。在我们的大部分工作中,这是user命名空间,因此提示将是。

user=>

Everything in the rest of this article will assume that we have access to the Clojure REPL, and will all work directly in any such tool.

本文的其余部分将假设我们能够访问Clojure REPL,并且所有这些将直接在任何此类工具中工作。

4. Language Basics

4.语言基础知识

The Clojure language looks very different from many other JVM based languages, and will possibly seem very unusual to start with. It’s considered to be a dialect of Lisp and has very similar syntax and functionality to other Lisp languages.

Clojure语言看起来与许多其他基于JVM的语言非常不同,而且在开始时可能会显得非常不寻常。它被认为是Lisp的一种方言,其语法和功能与其他Lisp语言非常相似。

A lot of the code that we write in Clojure – as with other Lisp dialects – is expressed in the form of Lists. Lists can then be evaluated to produce results – either in the form of more lists or simple values.

我们在Clojure中编写的很多代码–和其他Lisp方言一样–都是以Lists的形式表达的。然后,列表可以被评估以产生结果–要么是更多列表的形式,要么是简单的值。

For example:

比如说。

(+ 1 2) ; = 3

This is a list consisting of three elements. The “+” symbol indicates that we are performing this call – addition. The remaining elements are then used with this call. Thus, this evaluates to “1 + 2”.

这是一个由三个元素组成的列表。符号 “+”表示我们正在执行这个调用–加法。剩余的元素将与这个调用一起使用。因此,这被评估为 “1 + 2″。

By using a List syntax here, this can be trivially extended. For example, we can do:

通过在这里使用List语法,这可以进行简单的扩展。例如,我们可以这样做。

(+ 1 2 3 4 5) ; = 15

And this evaluates to “1 + 2 + 3 + 4 + 5”.

而这评估为 “1+2+3+4+5″。

Note as well the semi-colon character. This is used in Clojure to indicate a comment and isn’t the end of the expression as we’d see in Java.

还要注意分号字符。这在Clojure中是用来表示注释的,而不是像我们在Java中看到的表达式的结束。

4.1. Simple Types

4.1.简单的类型

Clojure is built on top of the JVM, and as such we have access to the same standard types as any other Java application. Types are typically inferred automatically and don’t need to be specified explicitly.

Clojure是建立在JVM之上的,因此我们可以像其他Java应用程序一样访问相同的标准类型。类型通常是自动推断出来的,不需要明确指定。

For example:

比如说。

123 ; Long
1.23 ; Double
"Hello" ; String
true ; Boolean

We can specify some more complicated types as well, using special prefixes or suffixes:

我们也可以使用特殊的前缀或后缀来指定一些更复杂的类型。

42N ; clojure.lang.BigInt
3.14159M ; java.math.BigDecimal
1/3 ; clojure.lang.Ratio
#"[A-Za-z]+" ; java.util.regex.Pattern

Note that the clojure.lang.BigInt type is used instead of java.math.BigInteger. This is because the Clojure type has some minor optimizations and fixes.

请注意,使用了clojure.lang.BigInt类型而不是java.math.BigInteger。这是因为Clojure类型有一些小的优化和修正。

4.2. Keywords and Symbols

4.2.关键词和符号

Clojure gives us the concept of both keywords and symbols. Keywords refer only to themselves and are often used for things such as map keys. Symbols, on the other hand, are names used to refer to other things. For example, variable definitions and function names are symbols.

Clojure给了我们关键词和符号的概念。关键词只指代自己,通常用于诸如地图键等。另一方面,符号是用来指代其他事物的名称。例如,变量定义和函数名称就是符号。

We can construct keywords by using a name prefixed with a colon:

我们可以通过使用一个以冒号为前缀的名称来构建关键词。

user=> :kw
:kw
user=> :a
:a

Keywords have direct equality with themselves, and not with anything else:

关键词与自己直接平等,而不是与其他东西平等:

user=> (= :a :a)
true
user=> (= :a :b)
false
user=> (= :a "a")
false

Most other things in Clojure that are not simple values are considered to be symbols. These evaluate to whatever they refer to, whereas a keyword always evaluates to itself:

Clojure中大多数不是简单值的其他东西都被认为是符号。这些东西对它们所指的东西进行评估,而关键字总是对它自己进行评估。

user=> (def a 1)
#'user/a
user=> :a
:a
user=> a
1

4.3. Namespaces

4.3.命名空间

The Clojure language has the concept of namespaces for organizing our code. Every piece of code we write lives in a namespace.

Clojure语言有命名空间的概念,用于组织我们的代码。我们写的每一段代码都生活在一个命名空间中。

By default, the REPL runs in the user namespace – as seen by the prompt stating “user=>”.

默认情况下,REPL在user命名空间中运行 – 正如提示 “user=>”所见。

We can create and change namespaces using the ns keyword:

我们可以使用ns关键字创建和改变命名空间

user=> (ns new.ns)
nil
new.ns=>

Once we’ve changed namespaces, anything that is defined in the old one is no longer available to us, and anything defined in the new one is now available.

一旦我们改变了命名空间,在旧的命名空间中定义的任何东西都不再对我们有用,而在新的命名空间中定义的任何东西现在都可用。

We can access definitions across namespaces by fully qualifying them. For example, the namespace clojure.string defines a function upper-case.

我们可以通过完全限定它们来访问跨命名空间的定义。例如,命名空间clojure.string定义了一个函数upper-case

If we’re in the clojure.string namespace, we can access it directly. If we’re not, then we need to qualify it as clojure.string/upper-case:

如果我们在clojure.string命名空间中,我们可以直接访问它。如果我们不在,那么我们需要把它限定为clojure.string/upper-case

user=> (clojure.string/upper-case "hello")
"HELLO"
user=> (upper-case "hello") ; This is not visible in the "user" namespace
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: upper-case in this context
user=> (ns clojure.string)
nil
clojure.string=> (upper-case "hello") ; This is visible because we're now in the "clojure.string" namespace
"HELLO"

We can also use the require keyword to access definitions from another namespace in an easier way. There are two main ways that we can use this – to define a namespace with a shorter name so that it’s easier to use, and to access definitions from another namespace without any prefix directly:

我们还可以使用require 关键字来以更简单的方式访问另一个命名空间的定义。我们有两种主要的使用方式–用更短的名字来定义一个命名空间,这样它就更容易被使用,以及直接从另一个命名空间访问定义而不需要任何前缀。

clojure.string=> (require '[clojure.string :as str])
nil
clojure.string=> (str/upper-case "Hello")
"HELLO"

user=> (require '[clojure.string :as str :refer [upper-case]])
nil
user=> (upper-case "Hello")
"HELLO"

Both of these only affect the current namespace, so changing to a different one will need to have new requires. This helps to keep our namespaces cleaner and give us access to only what we need.

这两种情况都只影响当前的命名空间,所以改变到不同的命名空间将需要有新的要求。这有助于保持我们的命名空间的清洁,并让我们只访问我们需要的东西。

4.4. Variables

4.4.变量

Once we know how to define simple values, we can assign them to variables. We can do this using the keyword def:

一旦我们知道如何定义简单的值,我们就可以把它们分配给变量。我们可以使用关键字def来做这件事。

user=> (def a 123)
#'user/a

Once we’ve done this, we can use the symbol a anywhere we want to represent this value:

一旦我们这样做了,我们就可以使用符号a 在任何我们想表示这个值的地方:

user=> a
123

Variable definitions can be as simple or as complicated as we want.

变量的定义可以很简单,也可以很复杂,只要我们想。

For example, to define a variable as the sum of numbers, we can do:

例如,要将一个变量定义为数字之和,我们可以这样做。

user=> (def b (+ 1 2 3 4 5))
#'user/b
user=> b
15

Notice that we never have to declare the variable or indicate what type it is. Clojure automatically determines all of this for us.

请注意,我们从来不需要声明这个变量或指出它是什么类型。Clojure会自动为我们决定这一切。

If we try to use a variable that has not been defined, then we will instead get an error:

如果我们试图使用一个没有被定义的变量,那么我们反而会得到一个错误。

user=> unknown
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: unknown in this context
user=> (def c (+ 1 unknown))
Syntax error compiling at (REPL:1:8).
Unable to resolve symbol: unknown in this context

Notice that the output of the def function looks slightly different from the input. Defining a variable a returns a string of ‘user/a. This is because the result is a symbol, and this symbol is defined in the current namespace.

注意,def函数的输出看起来与输入略有不同。定义一个变量a返回一个‘user/a的字符串。这是因为结果是一个符号,而这个符号是在当前命名空间中定义的。

4.5. Functions

4.5.职能

We’ve already seen a couple of examples of how to call functions in Clojure. We create a list that starts with the function to be called, and then all of the parameters.

我们已经看过几个关于如何在Clojure中调用函数的例子。我们创建一个列表,以要调用的函数开始,然后是所有的参数。

When this list evaluates, we get the return value from the function. For example:

当这个列表进行评估时,我们就会得到函数的返回值。比如说。

user=> (java.time.Instant/now)
#object[java.time.Instant 0x4b6690c0 "2019-01-15T07:54:01.516Z"]
user=> (java.time.Instant/parse "2019-01-15T07:55:00Z")
#object[java.time.Instant 0x6b8d96d9 "2019-01-15T07:55:00Z"]
user=> (java.time.OffsetDateTime/of 2019 01 15 7 56 0 0 java.time.ZoneOffset/UTC)
#object[java.time.OffsetDateTime 0xf80945f "2019-01-15T07:56Z"]

We can also nest calls to functions, for when we want to pass the output of one function call in as a parameter to another:

我们还可以对函数进行嵌套调用,当我们想把一个函数调用的输出作为参数传递给另一个函数时。

user=> (java.time.OffsetDateTime/of 2018 01 15 7 57 0 0 (java.time.ZoneOffset/ofHours -5))
#object[java.time.OffsetDateTime 0x1cdc4c27 "2018-01-15T07:57-05:00"]

Also, we can also define our functions if we desire. Functions are created using the fn command:

此外,如果我们愿意,我们也可以定义我们的函数函数是用fn命令创建的

user=> (fn [a b]
  (println "Adding numbers" a "and" b)
  (+ a b)
)
#object[user$eval165$fn__166 0x5644dc81 "user$eval165$fn__166@5644dc81"]

Unfortunately, this doesn’t give the function a name that can be used. Instead, we can define a symbol that represents this function using def, exactly as we’ve seen for variables:

不幸的是,这并没有给这个函数一个可以使用的名字。相反,我们可以使用def来定义一个代表这个函数的符号,就像我们看到的变量一样。

user=> (def add
  (fn [a b]
    (println "Adding numbers" a "and" b)
    (+ a b)
  )
)
#'user/add

Now that we’ve defined this function, we can call it the same as any other function:

现在我们已经定义了这个函数,我们可以像其他函数一样调用它。

user=> (add 1 2)
Adding numbers 1 and 2
3

As a convenience, Clojure also allows us to use defn to define a function with a name in a single go.

为了方便起见,Clojure还允许我们使用defn来一次性定义一个有名字的函数

For example:

比如说。

user=> (defn sub [a b]
  (println "Subtracting" b "from" a)
  (- a b)
)
#'user/sub
user=> (sub 5 2)
Subtracting 2 from 5
3

4.6. Let and Local Variables

4.6.让和局部变量

The def call defines a symbol that is global to the current namespace. This is typically not what is desired when executing code. Instead, Clojure offers the let call to define variables local to a block. This is especially useful when using them inside functions, where you don’t want the variables to leak outside of the function.

def调用定义了一个对当前命名空间来说是全局的符号。在执行代码时,这通常不是我们所需要的。相反,Clojure提供了let调用来定义一个块的局部变量。在函数内部使用这些变量时,这一点特别有用,因为你不希望这些变量泄露到函数之外。

For example, we could define our sub function:

例如,我们可以定义我们的子函数。

user=> (defn sub [a b]
  (def result (- a b))
  (println "Result: " result)
  result
)
#'user/sub

However, using this has the following unexpected side effect:

然而,使用这种方法有以下意外的副作用。

user=> (sub 1 2)
Result:  -1
-1
user=> result ; Still visible outside of the function
-1

Instead, let’s re-write it using let:

相反,让我们用let重写它。

user=> (defn sub [a b]
  (let [result (- a b)]
    (println "Result: " result)
    result
  )
)
#'user/sub
user=> (sub 1 2)
Result:  -1
-1
user=> result
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: result in this context

This time the result symbol is not visible outside of the function. Or, indeed, outside of the let block in which it was used.

这一次,result符号在函数之外不可见。或者,事实上,在使用它的let块之外。

5. Collections

5.收藏

So far, we’ve been mostly interacting with simple values. We have seen lists as well, but nothing more. Clojure does have a full set of collections that can be used, though, consisting of lists, vectors, maps, and sets:

到目前为止,我们主要是与简单的值进行交互。我们也看到了列表,但没有别的。Clojure确实有一整套可以使用的集合,包括列表、向量、地图和集合

  • A vector is an ordered list of values – any arbitrary value can be put into a vector, including other collections.
  • A set is an unordered collection of values, and can never contain the same value more than once.
  • A map is a simple set of key/value pairs. It’s very common to use keywords as the keys in a map, but we can use any value we like, including other collections.
  • A list is very similar to a vector. The difference is similar to that between an ArrayList and a LinkedList in Java. Typically, a vector is preferred, but a list is better if we want to be adding elements to the start, or if we only ever want to access the elements in sequential order.

5.1. Constructing Collections

5.1.构建集合

Creating each of these can be done using a shorthand notation or using a function call:

创建每一个都可以使用速记符号或使用函数调用来完成。

; Vector
user=> [1 2 3]
[1 2 3]
user=> (vector 1 2 3)
[1 2 3]

; List
user=> '(1 2 3)
(1 2 3)
user=> (list 1 2 3)
(1 2 3)

; Set
user=> #{1 2 3}
#{1 3 2}
user=> (hash-set 1 2 3)
#{1 3 2}

; Map
user=> {:a 1 :b 2}
{:a 1, :b 2}
user=> (hash-map :a 1 :b 2)
{:b 2, :a 1}

Notice that the Set and Map examples don’t return the values in the same order. This is because these collections are inherently unordered, and what we see depends on how they are represented in memory.

请注意,SetMap的例子并没有按照相同的顺序返回值。这是因为这些集合本质上是无序的,我们看到的东西取决于它们在内存中的表示方式。

We can also see that the syntax for creating a list is very similar to the standard Clojure syntax for expressions. A Clojure expression is, in fact, a list that gets evaluated, whereas the apostrophe character here indicates that we want the actual list of values instead of evaluating it.

我们还可以看到,创建一个列表的语法与表达式的标准Clojure语法非常相似。Clojure表达式实际上是一个被评估的列表,而这里的撇号字符表示我们想要实际的值列表,而不是评估它。

We can, of course, assign a collection to a variable in the same way as any other value. We can also use one collection as a key or value inside another collection.

当然,我们可以像对待其他值一样,将一个集合分配给一个变量。我们也可以将一个集合作为另一个集合内部的键或值。

Lists are considered to be a seq. This means that the class implements the ISeq interface. All other collections can be converted to a seq using the seq function:

Lists被认为是一个seq。这意味着该类实现了ISeq接口。所有其它的集合都可以使用seq函数转换为seq

user=> (seq [1 2 3])
(1 2 3)
user=> (seq #{1 2 3})
(1 3 2)
user=> (seq {:a 1 2 3})
([:a 1] [2 3])

5.2. Accessing Collections

5.2.访问集合

Once we have a collection, we can interact with it to get values back out again. How we can do this depends slightly on the collection in question, since each of them has different semantics.

一旦我们有了一个集合,我们就可以与它进行交互,以再次获得值。我们如何做到这一点,稍微取决于有关的集合,因为它们每个都有不同的语义。

Vectors are the only collection that lets us get any arbitrary value by index. This is done by evaluating the vector and index as an expression:

向量是唯一能让我们通过索引获得任何任意值的集合。这是通过评估向量和索引作为一个表达式来实现的。

user=> (my-vector 2) ; [1 2 3]
3

We can do the same, using the same syntax, for maps as well:

我们也可以使用同样的语法对地图进行处理

user=> (my-map :b)
2

We also have functions for accessing vectors and lists to get the first value, last value, and the remainder of the list:

我们还有一些函数用于访问向量和列表,以获得列表的第一个值、最后一个值和剩余部分:

user=> (first my-vector)
1
user=> (last my-list)
3
user=> (next my-vector)
(2 3)

Maps have additional functions to get the entire list of keys and values:

地图有额外的函数来获得整个键和值的列表:

user=> (keys my-map)
(:a :b)
user=> (vals my-map)
(1 2)

The only real access that we have to sets is to see if a particular element is a member.

我们对集合的唯一真正访问是查看某个特定元素是否是成员。

This looks very similar to accessing any other collection:

这看起来与访问任何其他收藏非常相似。

user=> (my-set 1)
1
user=> (my-set 5)
nil

5.3. Identifying Collections

5.3.鉴别藏品

We’ve seen that the way we access a collection varies depending on the type of collection we have. We have a set of functions we can use to determine this, both in a specific and more generic manner.

我们已经看到,根据我们所拥有的集合的类型,我们访问集合的方式是不同的。我们有一组函数可以用来确定这一点,既有特定的方式,也有更通用的方式。

Each of our collections has a specific function to determine if a given value is of that type – list? for lists, set? for sets, and so on. Additionally, there is seq? for determining if a given value is a seq of any kind, and associative? to determine if a given value allows associative access of any kind – which means vectors and maps:

我们的每个集合都有一个特定的函数来确定一个给定的值是否属于该类型–list?用于列表,set?用于集合,等等。此外,还有seq?用于确定给定值是否是任何类型的seq,以及associative?用于确定给定值是否允许任何类型的关联访问–这意味着向量和地图。

user=> (vector? [1 2 3]) ; A vector is a vector
true
user=> (vector? #{1 2 3}) ; A set is not a vector
false
user=> (list? '(1 2 3)) ; A list is a list
true
user=> (list? [1 2 3]) ; A vector is not a list
false
user=> (map? {:a 1 :b 2}) ; A map is a map
true
user=> (map? #{1 2 3}) ; A set is not a map
false
user=> (seq? '(1 2 3)) ; A list is a seq
true
user=> (seq? [1 2 3]) ; A vector is not a seq
false
user=> (seq? (seq [1 2 3])) ; A vector can be converted into a seq
true
user=> (associative? {:a 1 :b 2}) ; A map is associative
true
user=> (associative? [1 2 3]) ; A vector is associative
true
user=> (associative? '(1 2 3)) ; A list is not associative
false

5.4. Mutating Collections

5.4.变异集合

In Clojure, as with most functional languages, all collections are immutable. Anything that we do to change a collection results in a brand new collection being created to represent the changes. This can give huge efficiency benefits and means that there is no risk of accidental side effects.

在Clojure中,与大多数函数式语言一样,所有的集合都是不可变的。我们对一个集合所做的任何改变都会导致一个全新的集合被创建来代表这些改变。这可以带来巨大的效率优势,并且意味着没有意外的副作用的风险。

However, we also have to be careful that we understand this, otherwise the expected changes to our collections will not be happening.

然而,我们也必须注意了解这一点,否则我们的收藏品就不会发生预期的变化。

Adding new elements to a vector, list, or set is done using conj. This works differently in each of these cases, but with the same basic intention:

向向量、列表或集合添加新元素是通过conj完成的。这在每种情况下的工作方式不同,但基本意图是一样的。

user=> (conj [1 2 3] 4) ; Adds to the end
[1 2 3 4]
user=> (conj '(1 2 3) 4) ; Adds to the beginning
(4 1 2 3)
user=> (conj #{1 2 3} 4) ; Unordered
#{1 4 3 2}
user=> (conj #{1 2 3} 3) ; Adding an already present entry does nothing
#{1 3 2}

We can also remove entries from a set using disj. Note that this doesn’t work on a list or vector, because they are strictly ordered:

我们也可以用disj从一个集合中删除条目。请注意,这对列表或向量不起作用,因为它们是严格有序的。

user=> (disj #{1 2 3} 2) ; Removes the entry
#{1 3}
user=> (disj #{1 2 3} 4) ; Does nothing because the entry wasn't present
#{1 3 2}

Adding new elements to a map is done using assoc. We can also remove entries from a map using dissoc:

向地图添加新元素是使用assoc完成的。我们还可以使用dissoc从地图中删除条目:

user=> (assoc {:a 1 :b 2} :c 3) ; Adds a new key
{:a 1, :b 2, :c 3}
user=> (assoc {:a 1 :b 2} :b 3) ; Updates an existing key
{:a 1, :b 3}
user=> (dissoc {:a 1 :b 2} :b) ; Removes an existing key
{:a 1}
user=> (dissoc {:a 1 :b 2} :c) ; Does nothing because the key wasn't present
{:a 1, :b 2}

5.5. Functional Programming Constructs

5.5.功能性编程结构

Clojure is, at its heart, a functional programming language. This means that we have access to many traditional functional programming concepts – such as map, filter, and reduceThese generally work the same as in other languages. The exact syntax may be slightly different, though.

Clojure的核心是一种函数式编程语言。这意味着我们可以使用许多传统的函数式编程概念–例如map, filter,reduce这些通常与其他语言的工作原理相同。不过,确切的语法可能略有不同。

Specifically, these functions generally take the function to apply as the first argument, and the collection to apply it to as the second argument:

具体来说,这些函数通常把要应用的函数作为第一个参数,而把要应用的集合作为第二个参数。

user=> (map inc [1 2 3]) ; Increment every value in the vector
(2 3 4)
user=> (map inc #{1 2 3}) ; Increment every value in the set
(2 4 3)

user=> (filter odd? [1 2 3 4 5]) ; Only return odd values
(1 3 5)
user=> (remove odd? [1 2 3 4 5]) ; Only return non-odd values
(2 4)

user=> (reduce + [1 2 3 4 5]) ; Add all of the values together, returning the sum
15

6. Control Structures

6.控制结构

As with all general purpose languages, Clojure features calls for standard control structures, such as conditionals and loops.

与所有通用语言一样,Clojure具有调用标准控制结构的功能,如条件语句和循环。

6.1. Conditionals

6.1.条件式

Conditionals are handled by the if statement. This takes three parameters: a test, a block to execute if the test is true, and a block to execute if the test is false. Each of these can be a simple value or a standard list that will be evaluated on demand:

条件是由if语句处理的。这需要三个参数:一个测试,一个在测试为true时执行的块,以及一个在测试为false时执行的块。每个参数都可以是一个简单的值或一个标准的列表,将按要求进行评估。

user=> (if true 1 2)
1
user=> (if false 1 2)
2

Our test can be anything at all that we need – it doesn’t need to be a true/false value. It can also be a block that gets evaluated to give us the value that we need:

我们的测试可以是任何我们需要的东西 – 它不需要是一个真/假值。它也可以是一个被评估的块,以提供我们需要的值。

user=> (if (> 1 2) "True" "False")
"False"

All of the standard checks, including =, >, and <, can be used here. There’s also a set of predicates that can be used for various other reasons – we saw some already when looking at collections, for example:

所有的标准检查,包括=, >,<,都可以在这里使用。还有一组谓词,可用于其他各种原因–例如,我们在看集合时已经看到了一些。

user=> (if (odd? 1) "1 is odd" "1 is even")
"1 is odd"

The test can return any value at all – it doesn’t need only to be true or false. However, it is considered to be true if the value is anything except false or nil. This is different from the way that JavaScript works, where there is a large set of values that are considered to be “truth-y” but not true:

该测试可以返回任何值–它不需要仅仅是truefalse。然而,如果值是除了falsenil以外的任何东西,它就被认为是true。这与JavaScript的工作方式不同,在JavaScript中,有一大批值被认为是 “真-Y”,但不是

user=> (if 0 "True" "False")
"True"
user=> (if [] "True" "False")
"True"
user=> (if nil "True" "False")
"False"

6.2. Looping

6.2.循环

Our functional support on collections handles much of the looping work – instead of writing a loop over the collection, we use the standard functions and let the language do the iteration for us.

我们对集合的功能支持处理了大部分的循环工作–我们不用在集合上写一个循环,而是使用标准函数,让语言为我们做迭代工作。

Outside of this, looping is done entirely using recursion. We can write recursive functions, or we can use the loop and recur keywords to write a recursive style loop:

在此之外,循环完全是使用递归来完成的。我们可以编写递归函数,或者使用loop recur 关键字来编写一个递归风格的循环。

user=> (loop [accum [] i 0]
  (if (= i 10)
    accum
    (recur (conj accum i) (inc i))
  ))
[0 1 2 3 4 5 6 7 8 9]

The loop call starts an inner block that is executed on every iteration and starts by setting up some initial parameters. The recur call then calls back into the loop, providing the next parameters to use for the iteration. If recur is not called, then the loop finishes.

loop 调用启动了一个内块,该内块在每次迭代时都会执行,并开始设置一些初始参数。然后recur调用回到循环中,提供下一个迭代所需的参数。如果recur没有被调用,那么循环就结束了。

In this case, we loop every time that the value is not equal to 10, and then as soon as it is equal to 10, we instead return the accumulated vector of numbers.

在这种情况下,我们在每次i 值不等于10时进行循环,然后只要它等于10,我们就转而返回累积的数字向量。

7. Summary

7.总结

This article has given an introduction to the Clojure programming language and shows how the syntax works and some of the things that you can do with it. This is only an introductory level and doesn’t go into the depths of everything that can be done with the language.

本文对Clojure编程语言进行了介绍,并展示了该语言的语法工作原理以及你可以用它做的一些事情。这只是一个介绍性的水平,并没有深入了解可以用该语言做的所有事情。

However, why not pick it up, give it a go and see what you can do with it.

然而,为什么不拿起它,给它一个机会,看看你能用它做什么。