ClojureClojure技术集合

Clojure 学习笔记 :1 初探 Clojure

2016-05-17  本文已影响934人  BlindingDark

Clojure 零基础 学习笔记


欢迎来到 Clojure 的世界。

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

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

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

首先说明本文的几个约定

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

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

所以我们可以大概知道 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 中所有的表达式都有值

上一篇 下一篇

猜你喜欢

热点阅读