Clojure 编程/常见问题解答
作为一种函数式语言,Clojure 不鼓励使用经典意义上的变量,即保存可变值的存储位置。如果一开始需要更多思考才能构建不需要变量的解决方案,请尝试付出努力 - 它会让你得到很多回报。例如
int i;
int sum;
for (i=1;i<=100;i++,sum+=i);
可以在 Clojure 中用不使用变量的方式编写为
(apply + (range 1 100))
Clojure 确实支持变量,但您应该在使用它们之前完全阅读有关它们的信息,以了解它们的语义。如果您真的被困住,想要快速解决问题,可以使用 def
(def a 5)
(def a 6)
但不建议这样做,仅建议将其用作解决方法,直到您能够更深入地探索 Clojure。
Clojure 还支持变量作为不可变值名称 - 意味着您不能在以后更改它。这些是通过将值绑定到名称来创建的,并且始终具有可以使用该名称的词法范围。函数参数就是一个这样的例子,您应该从其他语言中熟悉它。
还有许多 Clojure 结构也创建临时绑定(其中一些结构不止一次)。最简单的是 let
(let [a 5, b 6] ; Comma is optional and treated as a whitespace
(+ a b))
(let [fact100 (factorial 100)]
(* fact100 fact100))
(zipmap [:a :b :c] [1 2 3])
;; ⇒ {:c 3, :b 2, :a 1}
假设我需要处理文本文件中的行,并且偶尔(根据行内容)创建对象并将它添加到结果列表中。(例如,对文本文件进行简单解析)。例如,也许每 5 或 6 行会有一条空行,我用它来根据前面的行创建对象。由于行数不同,我不能使用 map 或任何函数式方法,至少看起来是这样。是否需要使用 with-local-vars,或者有更优雅的方法?
以下代码搜索一组行并返回匹配的行。def 行只是为我们提供了一个可以处理的集合。for 语句迭代 "lines" 并将每个条目分配给 "line"。对于每行,都会调用 :when 子句。如果返回 true,则执行 for 的主体,在本例中,只需返回 line。结果被放入一个序列中,与 map 函数相同,但只包含匹配的元素。
(def lines ["aa" "bb" "cc" "dd"])
(for [line lines :when (= "bb" line)] line)
;; ⇒ ("bb")
filter 函数可以实现类似的功能
(filter #{"bb"} lines)
;; ⇒ ("bb")
使用 Leiningen,要创建库 foo
lein new foo
Generating a project called foo based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.
使用默认的 project.clj
(defproject foo "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]])
注意,按照 Leiningen 的术语,"foo" 是工件的名称,"0.1.0-SNAPSHOT" 是版本号。
转到新创建的项目 foo 的根目录。实现函数。安装库。
lein install
Created D:\yushen\dev\foo\target\foo-0.1.0-SNAPSHOT.jar
Wrote D:\yushen\dev\foo\pom.xml
Installed jar and pom into local repo.
上述内容适用于 Leiningen 2.4.2。
设置项目 try-foo
lein new try-foo
Generating a project called foo based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.
自定义 project.clj 文件,以表示对库 foo 的依赖关系
(defproject try-foo "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]
[foo "0.1.0-SNAPSHOT"]])
然后打开连接到项目的 repl
(use 'foo.core)
(foo "Hi")
库应该可以使用了。
顺序查找不是一项重要的操作。Clojure 包含集合和映射,如果您要进行查找,则应该使用它们。contains? 映射到 java.util.Set.contains。java.util.Collection 也包含 contains 的事实,在我看来,是一个错误,因为您实际上无法为具有极大差异性能特征的接口编写代码。因此,集合和映射中的查找优先,并获得最佳名称 - contains?。人们使用其他语言中的 contains() 编写幼稚的程序,并获得相应的糟糕的 n 平方性能 - 这不是鼓励 Clojure 中的论据。如果他们能解释为什么这是一个坏主意,以及如何使用集合和映射,那就没问题了。
您可能在使用某些惰性序列的副作用时发现了一些意想不到的事情
(first (for [i (range 10)] (prn i)))
;; => 0 1 2 3 4 5 6 7 8 9
请参阅 De-chunkifying sequences in Clojure
对于将文档简单地 GET 到字符串中的情况,你可以简单地将 URL 传递给 clojure.contrib.duck-streams/slurp*。
(use 'clojure.contrib.duck-streams)
(slurp* "http://www.example.org/")
;; ⇒ "<HTML>\r\n<HEAD>\r\n <TITLE>Example Web Page</TITLE>
对于更高级的用法,包括其他请求类型(如 POST),你可能会发现 clojure-http-client 库很有用。最后,你当然可以直接在 Java URL 对象 上使用 (.openConnection)。
你可以使用 clojure-contrib "show"
(use '[clojure.contrib.repl-utils :only (show)])
(show Object)
;; ⇒
;; === public java.lang.Object ===
;; [ 0] <init> ()
;; [ 1] equals : boolean (Object)
;; [ 2] getClass : Class ()
;; [ 3] hashCode : int ()
;; [ 4] notify : void ()
;; [ 5] notifyAll : void ()
;; [ 6] toString : String ()
;; [ 7] wait : void ()
;; [ 8] wait : void (long)
;; [ 9] wait : void (long,int)
或者,如果你不想使用 "clojure.contrib",那么以下方法应该可以完成部分工作
(map #(.getName %) (.getMethods Object))
;; ⇒ ("wait" "wait" "wait" "hashCode" "getClass" "equals" "toString" "notify" "notifyAll")
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
;; ⇒ #<HashMap {b=2, a=1}>
(def hm (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2)))
hm
;; ⇒ #<HashMap {b=2, a=1}>
(def h (into {} hm))
h
;; ⇒ {"a" 1, "b" 2}
(h "a")
;; 1
Clojure 不是面向对象的(至少不是传统意义上的)。Clojure 的核心前提之一是,在传统的面向对象方式中混淆状态和标识是一个坏主意,尤其是在面对并发的情况下。关于状态和标识 以及 Rich Hickey 的 我们到那里了吗? 演讲解释了这种想法背后的部分原因。
另请参阅 Stuart Halloway 的 Rifle-Oriented Programming 文章,该文章解释了 Clojure 对面向对象编程的关注点的一些答案。
当然,仅仅因为该语言不鼓励这样做并不意味着在 Clojure 中编写面向对象代码是不可能的——在一定程度上,这可能是在与 Java 代码交互时所必需的。但是,传统的 OO 样式编程在 Clojure 中是非惯用的,并且不鼓励这样做。
确保你正在使用 paredit 版本 22(beta)
当你用 M-x slime 启动它时,它会将 ~/.clojure 和 ~/.swank-clojure 下的所有 jar 文件添加到你的类路径中。你至少需要 clojure.jar、clojure-contrib.jar 和 swank-clojure.jar。你可以从 build.clojure.org 下载预先构建的 Clojure 和 Contrib jar 文件,并从 Clojars 下载 swank-clojure.jar。
当你使用 M-x swank-clojure-project 并指定一个目录时,swank-clojure 会将以下条目添加到类路径中
- src/
- classes/
- lib/*.jar
你需要手动将你想要使用的 clojure、contrib 和 swank-clojure jar 版本添加到 lib 目录中,或者使用 Leiningen 或 Maven 等依赖项获取工具。
你可能缺少 swank-clojure.jar。请参阅前一个问题。
Clojure 目前针对 Java 5(以及更高版本)。
Clojure 1.0 和 1.1 都被许多人在不同版本的
- Sun 的 JRE 1.5.0 和 1.6.0
- OpenJDK 1.5.0 和 1.6.0
- IBM J9
上经常使用。它还已知可以在以下平台上运行:
- 带有 Classpath 的 JamVM