计算(Evaluation)
user=> "hi"
;; "hi"
user=> :foo
;; :foo
user=> [1 2 3]
;; [1 2 3]
只要有表达式,clojure都会尝试去计算,并且重复打印在控制台。如果是括号开头"(",clojure会把它当成宏,特殊形式或者函数。
函数访问(Function Calls)`
(my-func arg1 arg2 arg3)
在括号"("右边的符号就是函数的名称(my-func就是函数名称),clojure先计算出所有参数的值,再把这些参数值赋给函数。
有点绕,我的理解是像下面这种嵌套调用,先从最里层开始计算,依次计算"+"->"foo-bar"->"other-func"->"my-func2"->"my-func"
(my-func (my-func2 arg1
arg2)
(other-func arg-a
(foo-bar arg-x
arg-y
(+ arg-xx
arg-yy
arg-zz))
arg-b))
主要注意的是,在clojure里面没有"操作符",像"+,-,*,>,=,not="都是函数名称。
宏与特殊形式(Macros and Special Form)
- 上面讲过,如果一个表达式以括号"("开头,clojure会先去检查是宏还是特殊形式,这些形式都区别于普通的计算,会被编译器特殊对待。
- 这里关于宏的描述有点拗口,我的理解是:使用普通的clojure代码编写,但是返回转换/扩展的代码在某些场合使用,即:可以通过宏产生出新的语法格式。先不管了,看了后面代码应该就明白了。
- 使用defmacro来定义宏'
- 宏在编译的时候被调用,并且在其他代码编译之前。就像if,def,let一样,这些都是硬编码到编译器,但效果是样的。
引号(Quoting)
'(+ 1 2 3)
;; ⇒ (+ 1 2 3)
如果是括号开头就会被当做函数,这种方式后面会介绍
Let and Locals
如果你想使用词法作用域,可以使用let表达式
(let [width 10
height 20
thickness 2]
(println "hello from inside the `let`.")
(* width
height
thickness))
;; ⇒ 400
再来一个列子,let会得到最后一个表达式的值。所以之前可以放一些其他的东西,例如println等。
(let [x 2
x (* x x)
x (+ x 1)]
x)
;; ⇒ 5
需要注意:println函数本身计算得到的值都是nil,我们不会用它来做任何计算,这种叫做side-effect,后面会简单介绍。
Namespaces
Clojure使用命名空间进行分组,避免函数名称冲突。所有的函数都有自己的命名空间,所有的core函数都在clojure.core这个命名空间下:
(clojure.core/println "hi")
- 这是println的fully-qualified(全名),这种格式是"namespace/symbol",举个例子,如果有个函数myfun在src/foo_bar/core.clj,那么可以使用foo_bar.core/myfun使用。
- 一般来说一个源文件对应一个namespace,通常包含一个库。在源文件的顶部,一般是ns xxx来声明namespace。
使用别名来引用库,这样就可以使用str/(为什么有斜杠还不清楚,后面应该会使用到)来代替clojure.string
(require '[clojure.string :as str])