Clojure 编程/教程和技巧
- 官方 Clojure 参考和 API : 链接
- R. Mark Volkmann 的 "Clojure - JVM 的函数式编程" : 链接
- Moxley Stratton - "面向非 Lisp 程序员的 Clojure 教程" : 链接
- Satish Talim - "Clojure 笔记" : 链接
- Eric Rochester - "Clojure 系列"(处理标记和词干提取) : 链接
- "面向 ImageJ 的 Clojure 教程" : 链接
- "Wikibooks.org/Clojure 编程/概念" : 链接
- Peter Seibel - "实用 Common Lisp" => 原文 ; 移植到 clojure
- Paul Graham - "On Lisp" => 原文 ; fogus 翻译了前 5 章 链接 ,Halloway 翻译了第 7、9、10 章 链接 ,pangloss 翻译了第 2 章到第 16 章的大部分内容 在 github 上
- "计算机程序的结构和解释" => 原文 ; 第 1 章移植到 clojure 链接
- "Clojure、Emacs 和 Slime/Swank 在 Ubuntu 8.10 上" : 链接
- Clojure 简明入门指南(上次更新: 2012-03)
Rich Hickey 做了一场名为 "面向 Java 程序员的介绍" 的演讲。音频和幻灯片在 Clojure blip.tv 频道 上分两部分提供。很好的 "Clojure 脚本" 教程 这里 涵盖了 Clojure 的许多基础知识,以及它的 Java 集成(例如,从 Clojure 使用 ImageJ)。
主站点的 Java 交互参考 展示了如何从 Clojure 调用 Java 代码。但由于 Clojure 是作为 Java 类库实现的,因此也可以轻松地将 Clojure 嵌入 Java 应用程序中,加载代码并调用函数。
主站点的 Java 交互参考 还展示了如何使用 Clojure 1.6 中引入的 clojure.java.api
包 来实现这一点。
当被问及是否使用 SICP 来学习 Clojure 时,Rich 说:[2008 年 3 月 26 日]
- 当然,每个人的经历都不一样。这是我的看法
- 我认为 SICP 不是关于编程语言的书。它是一本关于编程的书。它使用 Scheme 因为它在许多方面是一种原子编程语言。Lambda 演算 + 尾调用优化 (TCO) 用于循环 + 延续用于控制抽象 + 语法抽象 (宏) + 可变状态用于你需要的时候。它非常小。它已经足够了。
- 这本书真正讨论的是编程中的问题。模块化、抽象、状态、数据结构、并发性等等。它提供了通用分派、对象、并发、惰性列表、(可变) 数据结构、"标记" 等的描述和玩具实现,旨在阐明问题。
- Clojure 不是一种原子编程语言。我太累/老/懒了,不想用原子编程。Clojure 提供了通用分派、关联映射、元数据、并发基础设施、持久数据结构、惰性序列、多态库等的生产实现。Clojure 已经提供了比你按照 SICP 中的步骤构建的某些事物更好的实现。
- 因此,SICP 的价值在于帮助你理解编程概念。如果你已经理解了这些概念,Clojure 让你能够更快地编写有趣且健壮的程序,IMO。我认为 Clojure 的核心并不比 Scheme 的核心大多少。Schemers 怎么看?
- 我认为在 Clojure 之前,Lisp 语言在函数式编程和列表方面引领了你走上了一条很好的道路,但当你需要编写真正程序时,它却让你陷入困境,因为这些数据结构(如果提供的话)是可变的且命令式的。早期的 Lisp 也是在普遍的进程内并发出现之前,以及在高性能多态分派(例如虚拟函数)作为库基础设施的价值得到充分理解之前设计的。它们的库的多态性明显有限。
- Stuart Halloway 的著作《Programming Clojure》现已作为电子书(测试版)发布,纸质版也将在不久后推出。当然,在 Scheme 语言中,除了标准之外,还提供更多完整的功能(大多数 Scheme 语言都是如此),但也没有关于这方面的书籍。两种情况下都只有文档。
- 在学习 Clojure 的过程中学习 Scheme 或 Common Lisp 语言是不错的选择。会有一些细节无法直接转换(从 Scheme 语言 - 没有 TCO、false/ nil/() 的差异、没有延续;从 CL 语言 - Lisp-1、符号/变量二分法)。但就我个人而言,我认为 SICP 不会对学习 Clojure 有太大帮助。YMMV。
函数类型 | Scheme 函数 | Clojure 函数 |
---|---|---|
列表 | cons | cons |
list? | seq? | |
car | first | |
cdr | rest | |
caar, cadr, ... | ffirst, fnext, ... |
Clojure for Common Lisp Programmers
[edit | edit source]下表列出了一些 Common Lisp 实体(函数、宏等)及其(大致)等效的 Clojure 实体。请注意,某些概念可能不是完全匹配。要了解 Clojure 实体的精确行为,建议读者参考 Clojure 主页提供的 Clojure 参考文档。请注意,这些差异中的一些可能是因为 Clojure 的渊源来自 Lisp-1,而不是 Lisp-2。
Clojure 参考文档还记录了与 Lisp 语言的常见差异,点击此处查看。
Common Lisp 功能 | Clojure 等效项 |
---|---|
load | load-file |
make-array 或 #(N N N N) | vector 或 [N N N N] |
#| |#(多行注释) | (comment ...) |
documentation | doc, find-doc, javadoc |
in-package | in-ns |
defstruct | defstruct 和 create-struct(在 1.3 版本中建议使用 defrecord 或 deftype) |
defun | defn |
inline | definline |
lambda | fn 或 阅读器宏 #(..) |
cons | cons, lazy-cons, conj |
car, first | first |
cdr, rest | rest |
#\x(字符) | \x |
, | ~ |
,@ | ~@ |
eq(及其变体) | = |
expt | Math/pow java.lang.Mathuser=> (Math/pow 10 2) 100.0 |
format | String/format java.lang.Stringuser=> (format "0x%x 0x%x 0x%x" 10 20 30) "0xa 0x14 0x1e" or user=> (clojure.pprint/cl-format false "~R" 42) "forty-two" |
gensym | gensym 或 在符号名称后加 #,因为 ` 可以自动生成符号。user=> `(x#) (x__2136) |
progn | do |
type-of | class |
typep | instance? |
let | let 和 绑定 |
do | loop + recur(loop [temp-one 1 temp-two 0] (if (= 3 temp-two) temp-one (recur (inc temp-one) (inc temp-one)))) => 3 |
cond | cond,去掉一层括号(cond test-form1 form1 test-form2 form2 ... :else ;; :else or non nil or true form) |
Hyperspec |
http://clojure.github.com/clojure/ |
Clojure for Python/Ruby/Perl Programmers
[edit | edit source]Ruby 中 Enumerable 和 Array 类所有方法的等效 Clojure 函数 列表。
Clojure 中的单元测试
[edit | edit source]Clojure 提供了多种单元测试解决方案。它们对测试方法有略微不同的理念。可以尝试使用每个解决方案,看看哪一个最符合你的测试理念。
test-is
[edit | edit source]Stuart Sierra 的 test-is 框架包含在 clojure.contrib 中。它允许你使用 "is" 宏标记函数定义,并在其中声明断言,如以下示例所示(来自代码)
(defn add2 ([x] (+ x 2)) {:test (fn [] (is (= (add2 3) 5)) (is (= (add2 -4) -2) (is (> (add2 50) 50)))}
测试也可以单独构建
(deftest test-new-fn (is (= (new-fn) "Awesome")))
想要了解更多信息,最好查看源代码本身,它位于 Google Code 上的 clojure-contrib 库中。
在最近的版本中,此函数已移至核心发行版中的 clojure.test 命名空间;clojure-contrib 中仍然存在一个兼容性库。
Fact
[edit | edit source]Fact 是 James Reeves 编写的单元测试库,其风格类似于 Ruby 中的 RSpec。使用这种方法,你可以将测试编写为“事实”,每个事实都有一个断言来证明该事实。
(fact "The length of a concatenated list is equal to the length of its parts" [xs (rand-seqs rand-ints) ys (rand-seqs rand-ints)] (= (count (concat xs ys)) (+ (count xs) (count ys))))
James 在 Google 集团中发布了关于此库的描述,点击此处查看。此库托管在 github 上,点击此处查看。
unit-test
[edit | edit source]unit-test 是一个 xUnit 风格的单元测试系统,它允许你使用 deftest 宏定义测试。测试包含各种断言,如果其中一个断言失败,则测试失败。
示例
(deftest my-example-test [] (let [x 1] (assert-equal 1 x "x is NOT one!")))
unit-test 的原始版本尚未随着 Clojure 的更改而更新(原始主页点击此处),但 Tyler McMullen 已对其进行了修补,并在 github 上发布了一个工作版本,点击此处查看。
Clojure 中的 Shebang 脚本
[edit | edit source]此方法只在 Linux 上测试过,但在其他 Un*x 系统上也应该可以运行。
将以下内容放入 command-line-args.clj 文件中
#^:shebang '[ exec java -cp "$HOME/.m2/repository/org/clojure/clojure/1.5.1/clojure-1.5.1.jar" clojure.main "$0" "$@" ] (prn *command-line-args*)
使用以下命令将其设为可执行文件
$ chmod 755 command-line-args.clj
然后使用参数运行它。
$ ~/src/clj/lab/command-line-args.clj a b c ("a" "b" "c")
这种方法的解释在 Clojure 集团的 这封邮件中进行了描述。
此方法的更现代版本是将 command-line-args.clj 编写为
":";exec java -cp "$HOME/path-to-clojure.jar" clojure.main $0 "$@" (ns command-line-args) (defn command-line? [] (.isAbsolute (java.io.File. *file*))) (defn main [] (println *command-line-args*)) (if (command-line?) (main))
它具有更简单的“shebang”行。
(main) 在从命令行调用脚本时始终执行,即使在没有参数的情况下调用时也是如此。
当 command-line-args 被另一个 Clojure 文件使用或需要时,(main) 不会执行。
此方法的灵感来自 这种 Emacs 脚本方法,并且出于相同的原因有效。
Clojure 的最近更新将 #! 设为从行尾到行尾的注释。使用修订版 1106 或更高版本,如果你创建了 clj 脚本(例如在 入门中描述的那些脚本),则可以像往常一样进行 shebang 脚本编写。
#! /usr/bin/env clj (println "Hello World!")
否则,你可以直接引用 java jar 文件,如下所示
#! /usr/bin/java -jar clojure.jar clojure.lang.Script
注意:此方法可能不适用于所有系统,因为在某些系统上只允许使用一个参数!
在 Windows 上,类似的方法适用于将 Clojure 脚本嵌入到批处理文件中
:x (comment @echo off java -cp clojure.jar clojure.main "%~f0" %* goto :eof ) (println "Hi!" *command-line-args*)
在此,第一行被 cmd.exe 看作是一个标签(因为开头是冒号),被 Clojure 看作是一个裸关键字,后面跟着一个多行注释的开始。下一行(到闭合括号)运行 Java,并指定适当的类路径,传递批处理文件名 ("%~f0") 和命令行参数 ("%*"),然后退出批处理文件(goto eof)。闭合括号终止 Clojure 注释,Clojure 解释文件中的其余部分。
将应用程序以独立的 .jar 文件形式分发
[edit | edit source](在 bash shell 下的 linux 系统上完成)
- 将 ./classes 和 ./ 添加到 CLASSPATH shell 变量中,并确保 Clojure 也使用此类路径(可能意味着编辑 clj bash 脚本)
bash# export CLASSPATH=./classes:./
- bash# mkdir -p project/{app,classes}
- bash# cd project
- 使用以下内容创建 Clojure 应用程序 app/hello.clj
(ns app.hello (:gen-class)) (refer 'clojure.core) ; not sure if this is necessary (defn -main [& args] (println "application works"))
- 使用 Clojure REPL/shell 编译 Clojure 应用程序
user=> (compile 'app.hello) app.hello user=> <CTRL><d>
- 已编译的应用程序位于 classes/app 中
bash# ls classes/app/ hello.class hello__init.class hello$_main__4.class
- 将 clojure.jar 解压缩到 ./classes 中
bash# unzip /opt/clojure/clojure.jar -d classes/.
- 删除 ./classes/META-INF
bash# rm -r ./classes/META-INF
- 创建一个名为 mf-app.txt 的清单文本文件,内容如下
Main-Class: app.hello Class-Path: .
(确保文件在 "." 字符后以换行符结尾)
- 创建 .jar 文件
bash# jar cmf mf-app.txt app.jar -C classes .
(命令以 "." 结尾)
- 尝试运行应用程序(app.jar)
bash# java -jar app.jar application works
使用 (ns) 宏
[edit | edit source]使用 (ns) 宏可能会有点棘手。以下是一些可能有所帮助的示例。
- 需要 clojure.contrib.str-utils 中的所有函数,但不要直接导入它们到命名空间中。
(ns foo (:require clojure.contrib.str-utils)) (clojure.contrib.str-utils/str-join ", " ["foo" "bar"])
- 将 clojure.contrib.str-utils 中的所有函数直接导入到命名空间中。
(ns foo (:use clojure.contrib.str-utils)) (str-join ", " ["foo" "bar"])
- 使用别名命名空间导入 clojure.contrib.str-utils 中的所有函数。
(ns foo (:require [clojure.contrib [str-utils :as str-utils]])) (str-utils/str-join "," ["foo" "bar"])
- 排除 Clojure 的“list”函数,以便你可以定义自己的同名函数。
(ns foo (:refer-clojure :exclude [list]))