跳转到内容

Clojure 编程/教程和技巧

来自维基教科书,开放世界的开放书籍

在线学习资料

[编辑 | 编辑源代码]
在线教程
[编辑 | 编辑源代码]
  • 官方 Clojure 参考和 API : 链接
  • R. Mark Volkmann 的 "Clojure - JVM 的函数式编程" : 链接
  • Moxley Stratton - "面向非 Lisp 程序员的 Clojure 教程" : 链接
  • Satish Talim - "Clojure 笔记" : 链接
  • Eric Rochester - "Clojure 系列"(处理标记和词干提取) : 链接
  • "面向 ImageJ 的 Clojure 教程" : 链接
  • "Wikibooks.org/Clojure 编程/概念" : 链接
移植到 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 上" : 链接
[编辑 | 编辑源代码]

介绍和技巧

[编辑 | 编辑源代码]

面向 Java 程序员的 Clojure

[编辑 | 编辑源代码]

Rich Hickey 做了一场名为 "面向 Java 程序员的介绍" 的演讲。音频和幻灯片在 Clojure blip.tv 频道 上分两部分提供。很好的 "Clojure 脚本" 教程 这里 涵盖了 Clojure 的许多基础知识,以及它的 Java 集成(例如,从 Clojure 使用 ImageJ)。

从 Java 调用 Clojure

[编辑 | 编辑源代码]

主站点的 Java 交互参考 展示了如何从 Clojure 调用 Java 代码。但由于 Clojure 是作为 Java 类库实现的,因此也可以轻松地将 Clojure 嵌入 Java 应用程序中,加载代码并调用函数。

主站点的 Java 交互参考 还展示了如何使用 Clojure 1.6 中引入的 clojure.java.api 来实现这一点。

面向 Scheme 程序员的 Clojure

[编辑 | 编辑源代码]

当被问及是否使用 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.Math
user=> (Math/pow 10 2)
100.0
format String/format java.lang.String
user=> (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/
http://docs.oracle.com/javase/7/docs/
http://www.oracle.com/technetwork/java/index.html

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 是 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]))
华夏公益教科书