以不变应万变
在之前的学习中我们学习了如何定义我们的集合,一个很自然的想法就是修改这个集合。
我们可以使用 assoc
函数来替换一个集合中的元素
assoc
函数的第一个参数是你要进行替换的集合,第二个参数是你要替换的元素所在“位置”,第三个参数是替换后的元素。
返回值是修改过后的集合的值。
然而,Clojure
里集合类型是不可变的。assoc
函数的替换其实并没有改变原集合元素的内容,它返回的是一个“新的”集合。
我们可以通过以下代码观察:
=> (def my-vec ["第一个元素" "第二个元素"])
=> (assoc my-vec 0 "first")
["first" "第二个元素"]
=> (print my-vec)
[第一个元素 第二个元素]
nil
在这段代码中,我们首先定义了一个 vector
,然后我们使用 assoc
函数来对这个 vector
进行“替换”。把 0
号元素替换为 “first"
。看起来我们已经改动了这个 my-vec
。但是当我们使用 print
函数来观察这个集合的时候却发现,他的值没有发生变化。
那我们如何保存改变之后的值呢?
我们可以再给这个改变之后的值取一个名字。
更常见的做法是把这个改变作为一个值,传递给下一个需要这个值的表达式(如一个函数。我们在之后的定义函数一节里再进行详细介绍)
我们为什么需要这种不可变的设计呢?
不再一一叙述每个参数是什么,大家在代码中观察,以及在自己的机器上运行观察结果,然后修改部分参数观察是否符合预期。
=> (def my-vec ["第一个元素" "第二个元素"])
("第零个元素" "第一个元素" "第二个元素")
对于 vector
来说是末尾,但是对 list
来说是头部:
=> (def my-list '("第一个元素" "第二个元素"))
#'my-clojure-study.core/my-list
=> (conj my-list "第三个元素")
("第三个元素" "第一个元素" "第二个元素")
=> my-list
("第一个元素" "第二个元素")
和conj
函数一样,它只保证以最快的速度合并。
如果你接触过其它“可变的”编程语言,那么你需要提醒自己,使用 定义的不是变量,而是一个不可变值。
而当你真的需要在两个操作之间交换一些信息,比如在多线程中共享一些资源,那么 Clojure
单独提供了一些类型。我们在以后的多线程部分再做详细讲解。