Clojure 学习笔记 :1 初探 Clojure

Clojure 零基础 学习笔记


欢迎来到 Clojure 的世界。

让我们先从最经典的 hello world 开始吧。
我们使用键盘在 REPL 的输入框里输入 (print "hello world!"),回车!
屏幕中就会显示:

=> (print "hello world!")
hello world!
nil

(如果你还不知道怎么启动 REPL,你可以看一下这篇文章:“最小化”运行 Clojure REPL

首先说明本文的几个约定

  • => 后面跟随的内容,表示在 REPL[1] 里输入的内容
  • => 之后另起新行出现的代码,为 REPL 返回 (即 REPL 中的 Print) 的内容
  • ;= 之后出现的内容,表示上一个表达式的值

如你所见,Clojure 所拥有的 REPL 环境可以快速地与你进行交互 --- 表达式[2]被立即执行。

现在让我们看看这三行代码分别表示什么吧

  • 第 1 行是我们在 REPL 中输入的内容,比如使用键盘输入。这段代码的含义是,告诉 REPL ,我们要执行 print 函数,以及提供函数所需的参数
  • 第 2 行的 hello world!print 函数的副作用[3]。(而我们所需要的正是这个副作用)
  • 第 3 行的 nilprint 函数的返回值[4]print 函数始终返回 nil。( nil 在 Clojure 中表示空值)

所以我们可以大概知道 REPL 是怎么运行的:
首先,他接受你的输入。
然后,执行你所输入的代码,如果有副作用就会触发副作用。
最后,它返回你所输入的代码的值。REPL 总是把你所输入的表达式的值在最后一行显示出来

函数,是 Clojure 里最为重要也是最为基本的组成部分。
就如同你在中学数学学习到的 f(x,y) 一样,函数一般由三部分组成:

  1. 函数的名称。
    print。在 Clojure 中,它写在在小括号 () 中的第一个位置。
  2. 函数的参数。
    如果函数可以接受多个参数,多个参数之间用空格隔开。(也可以用 ,
  3. 函数的返回值。
    也就是函数的值。

所以 f(x,y) 在 Clojure里就表示为 (f x y)

此例中的 print 函数
它接收任意数量的参数,
它的返回值永远是 nil,也就是空,空值。

print 函数除了返回值之外,还拥有一个“副作用”,那就是它会依次把每个参数的显示在屏幕上 。(准确来说是 *out* 输出流)

函数像是一个黑盒子,你往里扔参数,他向你扔出返回值。
假如除此之外,这个黑盒子还打了你一巴掌,那这一巴掌就是函数的“副作用”。
如果你是为了得到你的返回值,那这个函数的“功能”就是返回的这个值。如果你想要享受痛苦,那这一巴掌就是他的“功能”。

这里我们显然利用的是 print 函数的副作用,对我们来说它才有用。
print 函数的返回值永远为 nil,所以也就不那么重要了。


Clojure 试图求值一切
函数的值等于它的返回值,而字符串的值就简单的等于他看起来的样子。
(双引号 "" 中的内容称之为字符串,它可以用来存储简单的文字或者数据,是程序设计语言中非常常见的 “明星” 。)

你可能对上面这一大堆话并不是很理解
没关系,我们多看例子

比如我们可以给 print 函数更多的参数

=> (print "hello world!" "hello again!" "bye!")
hello world! hello again! bye!
nil

或者一个参数也不给它

=> (print)
nil

观察结果
我们看到 print 函数果然显示了它的副作用 --- 依次显示每个参数的值。
例外地,如果没有参数,它自然也就没有副作用可以被触发。
最后,它的返回值 nil 总是在最后一行被显示。


Clojure 的“括号表示法”是可以嵌套的

=> (print (print "I love Rock!!!"))
I love Rock!!!nil
nil

为什么会出现这种结果呢?
重复一遍,Clojure 试图求值一切内容
函数的值是它的返回值,字符串的值是它本身…
这个例子的执行步骤是这样的

  1. 从左往右,找到第一个括号要执行的函数为 print
  2. print 函数的副作用是打印每个参数的值
  3. 但是这个参数的值无法直接确定,因为它并不是一个可以被直接求值的东西 --- 它又是一个函数。而函数也是有值的,函数的值就是它的返回值!
  4. 程序转而执行内层的 (print "I love Rock!!!") 。字符串的值可以直接被得到。所以内层 print 函数发现它所有的参数都可以直接被求值。于是它就开始发挥它的副作用了 --- 把每个参数的值打印出来,I love Rock!!! 就显示出来了。
  5. 此时内层函数的值确认了 --- **内层 print 函数的值等于它的返回值 nil **(虽然你一眼就能知道返回值永远为 nil,但计算机程序没有这个本事,它只能执行之后才能知道)
  6. 外层函数发现内层所有的参数都已经求值完毕,
    (如果这个时候时间静止的话,由于内层的“谜题”已经被解开,那我们的代码可能就会变成像这个样子)
 (print nil)

此时外层 print 函数的副作用发生!输出每个参数的值,即输出内层函数的值 --- nil

  1. 最后外层函数返回值 nil 显示在屏幕上。

如果你使用一些集成开发环境,那么你可以看到 print 函数的副作用所显示的 nilprint 函数的返回值 nil 的显示效果(如颜色和字体)看起来是不同的

一整句嵌套的表达式的返回值只有一个!它取决于最外层的那个函数的返回值!此例中即为最外层的那个print 的值 nil

同样,你可能对上面这一大堆话并不是很理解
我们再来几个例子
这次来介绍一个新的函数 println
它与print 函数的唯一不同在于,每次产生副作用打印时,自动在末尾换行

=> (println (println "I love Rock!!!"))
I love Rock!!!
nil
nil
=> (println (print "I love Rock!!!") (println "I love Rock too!!!") (print "I love you..."))
I love Rock!!!I love Rock too!!!
I love you...nil nil nil
nil

可以看到,最外层 println 函数在等待所有参数的值依次求值完毕后,副作用发生,一次性输出了三个 nil ,然后显示了自己的返回值
函数返回值是自动换行显示的(有些 REPL 环境并不自动换行,取决于具体实现),println 函数的换行效果指的是在副作用的末尾换行,即打印完毕后换行,此例中是在 "I love Rock too!!!" 后换了一行


作为一个程序设计语言,计算自然是最基础的。
但与其它语言或者日常习惯不同的一点,Clojure 的计算表示使用前缀表达式,即“运算符”放在“操作数”之前。
在 Clojure 里,运算符号同样是个普通的函数(甚至不是一个关键字)
而函数理所当然要放在括号的第一个位置

=> (+ 1 1)
2

加法函数 + 接收任意数量的表达式作为参数,它的返回值是各个参数的和,它没有副作用。
同样,加法函数是可以嵌套的

=> (+ 3 (+ 1 22))
26

等价于

=> (+ 3 1 22)
26

在上面的嵌套示例中, + 两个参数是 3(+ 1 22) 这两个表达式。(数字 3 也是一个正确的表达式)这两个表达式之间最好加上一个空格,这样会使得代码的层次感更好,也遵循了参数之间使用空格隔开的规则。
与之前的例子相似,在遇到有参数需要进一步求值时,会先求内层的值
这种做法使得你无需记忆无趣又无用的运算优先级
因为每个运算符号一定在括号的第一个位置,所以你总是能一层一层的找到唯一的计算顺序

=> (+ 2 (* 8 2));等价于中缀表达式 2 + 8 * 2
18

=> (* 2 (+ 8 2));等价于中缀表达式 2 * (8 + 2)
20

现在你已经初步了解了 Clojure 的执行过程与它的语法
接下来你会逐渐适应这种看似奇怪的表达方式
最终陶醉于这种表达方式所带来的优雅、简洁和便利
以及这种强大的语言所产生的无法抗拒的魅力


  1. REPL 即 Read-Eval-Print Loop --- “读取-求值-输出” 循环

  2. 表达式:你可以简单理解为一段可以被 Clojure 所执行的代码

  3. 副作用(Side effect):副作用是指,表达式被求值后,对外部世界的状态做的某些改变。当我们对一个如 (+ 1 2)
    这样纯粹的 Lisp 表达式求值时,没有产生副作用。它只返回一个值。但当我们调用 print 时,它不仅返回值,还印出了某些东西。这就是一种副作用。(引用自ANSI Common Lisp 中文翻譯版

  4. 返回值即为表达式执行后的值,同时是表达式本身的值,Clojure 中所有的表达式都有值

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

推荐阅读更多精彩内容