跳转到内容

Clojure 编程/常见问题解答

来自 Wikibooks,开放世界中的开放书籍

您希望在常见问题解答中看到什么?

[编辑 | 编辑源代码]

基础知识

[编辑 | 编辑源代码]

如何声明变量?

[编辑 | 编辑源代码]

作为一种函数式语言,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")

如何在本地创建 Clojure 库并将其与 Leiningen 一起使用?

[编辑 | 编辑源代码]

创建 Clojure 库

[编辑 | 编辑源代码]

使用 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")

库应该可以使用了。

为什么 contains? 在向量和列表上没有按照我的预期执行

[编辑 | 编辑源代码]

顺序查找不是一项重要的操作。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

如何获取网页并发出 HTTP 请求?

[编辑 | 编辑源代码]

对于将文档简单地 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)。

Java 交互

[编辑 | 编辑源代码]

如何查找 Java 对象包含哪些方法

[编辑 | 编辑源代码]

你可以使用 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")

如何将 Java HashMap 转换为 Clojure 映射?

[编辑 | 编辑源代码]
 (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 是面向对象的语言吗?

[编辑 | 编辑源代码]

Clojure 不是面向对象的(至少不是传统意义上的)。Clojure 的核心前提之一是,在传统的面向对象方式中混淆状态和标识是一个坏主意,尤其是在面对并发的情况下。关于状态和标识 以及 Rich Hickey 的 我们到那里了吗? 演讲解释了这种想法背后的部分原因。

另请参阅 Stuart Halloway 的 Rifle-Oriented Programming 文章,该文章解释了 Clojure 对面向对象编程的关注点的一些答案。

当然,仅仅因为该语言不鼓励这样做并不意味着在 Clojure 中编写面向对象代码是不可能的——在一定程度上,这可能是在与 Java 代码交互时所必需的。但是,传统的 OO 样式编程在 Clojure 中是非惯用的,并且不鼓励这样做。

Emacs 和 Clojure

[编辑 | 编辑源代码]

我已经安装了 paredit.el,但它不适用于花括号! {}

[编辑 | 编辑源代码]

确保你正在使用 paredit 版本 22(beta)

swank-clojure(SLIME)在哪里查找 Clojure 的 jar 文件?

[编辑 | 编辑源代码]

当你用 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 等依赖项获取工具。

Slime 停滞在消息:轮询 "/tmp/slime.####".. (使用 `M-x slime-abort-connection' 中止。)

[编辑 | 编辑源代码]

你可能缺少 swank-clojure.jar。请参阅前一个问题。

JVM 和 Clojure

[编辑 | 编辑源代码]

支持哪些版本的 Java?

[编辑 | 编辑源代码]

Clojure 目前针对 Java 5(以及更高版本)。

测试过哪些版本的 Java?

[编辑 | 编辑源代码]

Clojure 1.0 和 1.1 都被许多人在不同版本的

  • Sun 的 JRE 1.5.0 和 1.6.0
  • OpenJDK 1.5.0 和 1.6.0
  • IBM J9

上经常使用。它还已知可以在以下平台上运行:

  • 带有 Classpath 的 JamVM
华夏公益教科书