内容提要
作者推出《Coffee Break Clojure》系列,轻松介绍Clojure与Lisp。Lisp即列表处理,代码与数据均为列表,函数调用写作(函数名 参数…)。核心是eval:原子自求值,列表作为形式求值,首元素为操作符。quote(')阻止求值,将代码当数据处理,由此体现“代码即数据”,可用cons、apply等变换代码,并借宏扩展语言。
延伸解读
为什么 Lisp 的代码看起来像列表?
文章指出,Lisp 即 LISt Processing,代码与数据都统一表示为列表。函数调用写作 (函数名 参数…),函数定义、宏、条件语句等也都是列表。这种一致性让 Clojure 程序成为相互嵌套的列表,几乎没有额外语法。理解这一点,就能明白为何 Lisp 的括号如此之多,以及为何代码和数据可以无缝转换。
eval 与 quote:区分代码和数据的开关
文章解释,eval 对原子求值得到自身,对列表求值则把首元素当操作符、其余当参数。quote(')阻止求值,将列表当作数据对象。例如 (eval '(list "a" "b" "c")) 返回列表,而 (eval (list "a" "b" "c")) 会因 "a" 被当作操作符而报错。这揭示了 Lisp 中代码与数据的边界由是否求值决定。
代码即数据带来的编程灵活性
由于代码即数据,可以用 cons、apply 等函数像操作数据一样变换代码。例如 (cons '+ (list 1 2 3)) 生成 (+ 1 2 3),再 eval 一次得到 6。文章还提到,宏可以在求值前变换代码,从而扩展语言。这种能力是 Lisp 系语言强大表达力的根源,但也要求开发者清晰理解求值时机。
Q&A
Clojure 和 Lisp 是什么关系?Lisp 为什么叫这个名字?
Clojure 是 Lisp 的一种方言。Lisp 的意思是 LISt Processing(列表处理),因为 Lisp 中代码和数据都用列表表示。
Clojure 中的函数调用和函数定义怎么写?
函数调用写作 (函数名 参数1 参数2 ...),例如 (get webhooks keyw-or-urls)。函数定义用 defn,例如 (defn function-name [arg1 arg2] (println "I'm a function!") (str arg1 arg2))。
Clojure 里的 eval 是什么?原子和列表在求值时有什么区别?
eval 是求值操作。原子(如数字、字符串)是自求值对象,求值得到自身;列表被求值时,第一个元素作为操作符,其余作为参数。例如 (eval 1) 返回 1,(eval '(list "a" "b" "c")) 返回 ("a" "b" "c")。
Clojure 中 quote 或单引号 ' 有什么作用?为什么需要它?
quote(或 ')阻止求值,将代码当作数据(对象)处理。例如 '(list "a" "b" "c") 不会被求值,而是得到列表本身。如果不加 quote,列表会被当作函数调用,可能导致错误。
为什么说 Clojure 中“代码即数据”?能举个例子吗?
因为代码和数据都是列表,区别仅在于是否被求值。例如 (cons '+ (list 1 2 3)) 返回 (+ 1 2 3),再 eval 一次得到 6。可以用 cons、apply 等函数变换代码。
Clojure 的宏(macro)是做什么的?
宏可以在求值前变换代码,用来构造方便的写法,按自己的想法扩展语言。具体内容会在后续文章中介绍。