Clojure 学习笔记 :4 以不变应万变

Clojure 零基础 学习笔记 不可变集合


一个抽象的过程就是寻找变化中的不变量

在之前的学习中我们学习了如何定义我们的集合,
一个很自然的想法就是修改这个集合。

复习
我们可以使用 assoc 函数来替换一个集合中的元素

=> (assoc ["第一个元素" "第二个元素"] 0 "first")
["first" "第二个元素"]

assoc 函数的第一个参数是你要进行替换的集合,第二个参数是你要替换的元素所在“位置”,第三个参数是替换后的元素。
(注意,索引位置以 0 开始。)
返回值是修改过后的集合的值。

然而,Clojure 里集合类型是不可变的。
assoc 函数的替换其实并没有改变原集合元素的内容,
它返回的是一个“新的”集合。(你不用担心浪费存储空间,这个“新”的集合大部分重用了“老”集合。)

我们可以通过以下代码观察:

=> (def my-vec ["第一个元素" "第二个元素"])
#'my-clojure-study.core/my-vec

=> (assoc my-vec 0 "first")
["first" "第二个元素"]

=> (print my-vec)
[第一个元素 第二个元素]
nil

在这段代码中,我们首先定义了一个 vector,
然后我们使用 assoc 函数来对这个 vector 进行“替换”。把 0 号元素替换为 "first"。看起来我们已经改动了这个 my-vec
但是当我们使用 print 函数来观察这个集合的时候却发现,他的值没有发生变化。

那我们如何保存改变之后的值呢?
我们可以再给这个改变之后的值取一个名字,

=> (def my-vec ["第一个元素" "第二个元素"])
#'my-clojure-study.core/my-vec
=> (def modified-vec (assoc my-vec 0 "first"))
#'my-clojure-study.core/modified-vec

=> (print my-vec)
[第一个元素 第二个元素]
nil

=> (print modifyed-vec)
[first 第二个元素]
nil

更常见的做法是把这个改变作为一个值,传递给下一个需要这个值的表达式(如一个函数。我们在之后的定义函数一节里再进行详细介绍)


在 Clojure 里,值是不可变的。
我们为什么需要这种不可变的设计呢?
一个很重要的原因就是,使用不可变的值可以更为简便的实现多线程,
当你持有一个值的引用的时候,你不用担心会有其它线程修改你的值。
(另一个“文绉绉”的说法是,这样更能实现一个“纯函数”,即不修改其它值或者其他状态的函数[1]。)


这里再介绍几个对集合进行操作的函数,
不再一一叙述每个参数是什么,大家在代码中观察,
以及在自己的机器上运行观察结果,然后修改部分参数观察是否符合预期。

  • 使用 cons 函数把一些元素添加到集合的头部
=> (def my-vec ["第一个元素" "第二个元素"])
#'my-clojure-study.core/my-vec
=> (cons "第零个元素" my-vec)
("第零个元素" "第一个元素" "第二个元素")
  • 使用 conj 函数来添加一些元素到集合中
=> (def my-vec ["第一个元素" "第二个元素"])
#'my-clojure-study.core/my-vec
=> (conj my-vec "第三个元素")
["第一个元素" "第二个元素" "第三个元素"]
=> (conj my-vec "第三个元素" "第四个元素")
["第一个元素" "第二个元素" "第三个元素" "第四个元素"]
 
=> my-vec
["第一个元素" "第二个元素"]

my-vec 的值并没有发生改变。

注意,conj 函数并不是把元素添加到末尾的位置,它只能保证以最快的速度添加进一种数据结构
对于 vector 来说是末尾,但是对 list 来说是头部:

=> (def my-list '("第一个元素" "第二个元素"))
#'my-clojure-study.core/my-list
=> (conj my-list "第三个元素")
("第三个元素" "第一个元素" "第二个元素")
=> my-list
("第一个元素" "第二个元素")

my-list 的值同样没有发生改变。

  • 使用 into 函数来把两个集合进行合并。
    conj 函数一样,它只保证以最快的速度合并。
=> (def vec-1 ["一号元素"])
#'my-clojure-study.core/vec-1
=> (def vec-2 ["二号元素" 3])
#'my-clojure-study.core/vec-2
 
=> (into vec-1 vec-2)
["一号元素" "二号元素" 3]
 
;list 的情况
=> (def list-1 '("first"))
#'my-clojure-study.core/list-1
=> (def list-2 '("second"))
#'my-clojure-study.core/list-2
 
=> (into list-1 list-2)
("second" "first")

如果你接触过其它“可变的”编程语言,那么你需要提醒自己,使用 def 定义的不是变量,而是一个不可变值。
而当你真的需要在两个操作之间交换一些信息,比如在多线程中共享一些资源,那么 Clojure 单独提供了一些类型。我们在以后的多线程部分再做详细讲解。


  1. 即,没有副作用的函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容