你见过任何Clojure代码吗?
有吗?你几乎肯定不会在这里看到任何新的或有趣的东西。
没有吗?让我们来改变这种情况吧!我当然不是专家:但我一直在阅读Russ Olsen写的 "Getting Clojure",所以我至少知道足够的东西来快速浏览。
基础知识
你好!
(println "Hello World")
现在还不算太奇怪。这一行周围有括号,但它与类似print "Hello World" 的东西相差不大。
但在这里,下面一行是将1到4的整数相加:
(+ 1 2 3 4) ; 10
如果你从未见过Lisp语言,那么这可能看起来很奇怪。Clojure是我的第一种Lisp方言,一开始肯定看起来很奇怪。但我惊讶地发现,我很快就欣赏到了小括号的清晰结构。
作为一种Lisp方言,Clojure的语法是小括号中的结构化数据。要在Clojure中实现任何事情,都必须在括号中进行。
Clojure中的注释是以分号为前缀的(双分号习惯上是指完全是注释的行)。
每一个(到目前为止我所看到的)Clojure调用的结构
(function arg0 arg1 arg2 arg3 … argN)
有了这些知识,我们就可以理解第一行(+ 1 2 3 4) 。+ 函数正在被调用,其参数是1 2 3 4 。在非Lisp语言中的对应行是1 + 2 + 3 + 4 。
Clojure的主要基础之一是一切都通过函数完成。算术、变量赋值、逻辑、条件,甚至地图中的键的查找都是通过将键作为一个函数来完成。
;; Arithmetic
(+ 1 2 3) ; 6
(/ 6 2) ; 3
(/ 24 2 3) ; 4
;; Variable Assignment
(def greeting "Hello")
;; Logic
(= 2 3) ; false
(= 2 2 2) ; true
(> 9 7 3) ; true
(> 9 4 5) ; false
;; Conditionals
(def a 9)
(def b 3)
(if (> a b) a b) ; 9
;; Map lookup
(def zork1 {:released 1980 :genre :text-adventure})
(:released zork1) ; 1980
(:genre zork1) ; :text-adventure
你会注意到,这些函数的参数比你想象的要多。在许多语言中,平等是一个两个参数的表达式,例如:a == b ,但在Clojure中,你可以相当明确地比较任意数量的参数。
另外,if 这一行可能看起来很奇怪。如果它比b大,则返回a,否则返回b。(我们稍后会对这个问题进行分析)。
(if (> a b) a b)
条件式
在Clojure中,条件式也是函数。if 函数被调用时有2个(可以是3个)参数:
- 如果第一个参数被评估为
true,那么第二个参数被评估,其结果成为整个语句的结果。 - 如果第一个参数的值是
false,那么结果是nil,或者可选的第三个参数被评估。
user=> (if (= 2 2) "yes 2 equals 2")
"yes 2 equals 2"
user=> (if (= 2 3) "yes 2 equals 2")
nil
现在,之前的if 语句就更有意义了:
(if (> a b) a b)
; ⌃⌃⌃⌃⌃⌃⌃ ⌃ ⌃
; │ │ │
; └────── first arg: the logic to evaluate
; │ │
; └──── second arg: evaluated if first arg is true
; │
; │
; └── third arg: evaluated if first arg is false
变量
在Clojure中定义变量也是一个函数。def 这个函数接受一个名字和任何可以评估为值的东西。
user=> (def number 123)
#'user/number
user=> number
123
user=> (+ number 4)
127
函数
作为一种函数式语言,函数自然是Clojure的核心所在。它们可以用fn 函数来定义,该函数接受一个参数的向量(基本上是一个数组),后面是要评估的语句。最后一条语句的结果将是调用该函数的返回结果。
(fn [n] (* 2 n))
我们可以将该函数分配给一个名称,即def
user=> (def doubler (fn [n] (* 2 n)))
#'user/doubler
user=> (doubler 3)
6
user=> (def printy-doubler (fn [n] (println "Doubling" n) (* 2 n)))
#'user/printy-doubler
user=> (printy-doubler 3)
Doubling 3
6
使用def 和fn 可以工作,但不是Clojure希望其程序员拥有的伟大经验。Clojure提供了defn 函数,可以在一次调用中轻松命名和定义一个函数。
user=> (defn tripler [n] (* 3 n))
#'user/tripler
user=> (tripler 3)
9
为常见的动作提供有用的函数这一主题是Clojure的一个主要特征。它提供了许多函数,仅仅是为了使其编程更简洁、更有表现力、更快乐。
常见的数据结构
Clojure拥有你应该期待的现代编程语言的常用数据结构。
作为一种函数式语言,Clojure有不可变的数据结构。如果你不熟悉它们的使用,快速的要点是,每当你操作数据时,例如向一个列表添加一个新的项目,那么你就不会变异该列表:一个新的列表被创建,并带有额外的项目。
矢量
在一个连续的内存块(即一个数组)中的项目的有序集合,用括号定义:
[1 2 3 "four" ["any data even another vector"]]
(def v [1 2 3 4])
(count v) ; 4
(first v) ; 1
(rest v) ; (2 3 4)
列表
列表也是一个有序的项目集合,但不是存储在一个连续的内存块中,而是作为链接列表实现。这对何时适合使用这两种数据结构产生了影响:例如,在列表的开头添加一个新项是很容易的,但在向量的开头添加一个新项则相对昂贵。
在实践中,向量的使用要普遍得多。
列表是用圆括号定义的,这意味着它们需要比我们所期望的稍作调整,这样Clojure就不会把它们与表达式混淆:我们需要在它们前面加上'
'(1 2 3 "four" ("a nested list"))
(def l '(1 2 3 4))
(count l) ; 4
(first l) ; 1
(rest l) ; (2 3 4)
逗号?
你可能已经注意到,上面的向量和列表都没有在值之间使用逗号。Clojure通过将它们视为空白,巧妙地消除了任何逗号参数。如果你必须使用,你可以使用它们,但通常的做法是简单地省略它们。
;; these vectors are all the same data
(= [1 2 3 4] [1,2,3,4] [1,,,,,2,3,4] [1,2,3 4]) ; true
;; commas are whitespace even for expressions
(+,1,2,3,4) ; 10
(+,1,2,3,4,) ; 10
地图
将键和值联系起来的数据结构(即字典或哈希值)。注意在键/值的集合之间不需要逗号(因为逗号是空白的)。
任何值都可以是一个键,但Clojure程序通常使用关键字作为键,例如::keyword 。关键词本身可以作为函数使用,以从地图中检索值。
;; a bare map
{:key "value" :another-key "another value"}
;; assigned to a variable
(def ps5 {:name "Playstation 5"
:publisher "Sony"
:released 2020})
(:publisher ps5) ; "Sony"
(keys ps5) ; (:name :publisher :released)
(conj ps5 {:output :hdmi})
;; {:name "Playstation 5", :publisher "Sony", :released 2020, :output :hdmi}
ps5
;; {:name "Playstation 5", :publisher "Sony", :released 2020}
注意,上面的conj (conjoin)函数并没有修改ps5变量。它返回一个带有额外数据的新地图。
文档
Clojure有内联文档,你可以在它的副本中访问:
user=> (doc conj)
-------------------------
clojure.core/conj
([coll x] [coll x & xs])
conj[oin]. Returns a new collection with the xs
'added'. (conj nil item) returns (item). The 'addition' may
happen at different 'places' depending on the concrete type.
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
user=> (doc doc)
-------------------------
clojure.repl/doc
([name])
Macro
Prints documentation for a var or special form given its name,
or for a spec if given a keyword
总结
你现在已经看到了一些Clojure了!这还不是一个完整的过程。这还不是对这种强大而富有表现力的语言的全面概述。Clojure还有很多结构,可以帮助你简单地编写强大的程序。
xkcd Lisp Cycles 漫画