• 初探Clojure
    • 让我们先从最经典的 hello world 开始吧。
    • 首先说明本文的几个约定
    • 现在让我们看看这三行代码分别表示什么吧
    • 函数,是 Clojure 里最为重要也是最为基本的组成部分。

    初探Clojure

    欢迎来到 Clojure 的世界。

    让我们先从最经典的 hello world 开始吧。

    我们使用键盘在 REPL 的输入框里输入 (print "hello world!"),回车!

    屏幕中就会显示:

    1. => (print "hello world!")
    2. hello world!
    3. 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。( nilClojure 中表示空值)

    所以我们可以大概知道 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 函数更多的参数

    1. => (print "hello world!" "hello again!" "bye!")
    2. hello world! hello again! bye!
    3. nil

    或者一个参数也不给它

    1. => (print)
    2. nil

    观察结果

    我们看到 print 函数果然显示了它的副作用 —- 依次显示每个参数的值。

    例外地,如果没有参数,它自然也就没有副作用可以被触发。

    最后,它的返回值 nil 总是在最后一行被显示。

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

    1. => (print (print "I love Rock!!!"))
    2. I love Rock!!!nil
    3. nil

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

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

      (如果这个时候时间静止的话,由于内层的“谜题”已经被解开,那我们的代码可能就会变成像这个样子)

      1. (print nil)
    7. 此时外层 print 函数的副作用发生!输出每个参数的值,即输出内层函数的值 —- nil
    8. 最后外层函数返回值 nil 显示在屏幕上。

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

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

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

    1. => (println (println "I love Rock!!!"))
    2. I love Rock!!!
    3. nil
    4. nil

    复杂的例子

    1. => (println (print "I love Rock!!!") (println "I love Rock too!!!") (print "I love you..."))
    2. I love Rock!!!I love Rock too!!!
    3. I love you...nil nil nil
    4. nil

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

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

    1. => (+ 1 1)
    2. 2

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

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

    等价于

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

    注意 3 和 (+ 1 22) 之间有个空格,因为这两个表达式为外层函数的两个参数,自然要用空格隔开(数字 3 也是一个正确的表达式)
    与之前的例子相似,在遇到有参数需要进一步求值时,会先求内层的值
    这种做法使得你无需记忆无趣又无用的运算优先级
    因为每个运算符号一定在括号的第一个位置,所以你总是能一层一层的找到唯一的计算顺序

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

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

    [1]: REPL 即 Read-Eval-Print Loop —- “读取-求值-输出” 循环 ↩

    [2]: 表达式:你可以简单理解为一个可以被 Clojure 所执行的代码 ↩

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

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