《Clojure程序设计》——第1章,第1.2节Clojure编程快速入门

简介:

本节书摘来自异步社区《Clojure程序设计》一书中的第1章,第1.2节Clojure编程快速入门,作者 【美】Stuart Halloway , Aaron Bedra,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.2 Clojure编程快速入门
Clojure程序设计
要运行Clojure及本书的示例代码,你需要两件东西。
Java运行时。请下载1并安装Java 5或是更高版本。Java 6具有显著的性能提升和更好的异常报告,如果可能就尽量选它吧。
Leiningen2。Leiningen是一个用于管理依赖项的工具,并且可以基于你的代码启动各种任务。在Clojure世界中,它是处理这项工作最常用的工具了。
你将会使用Leiningen来安装Clojure和本书所有示例代码的依赖项。如果你已经安装了Leiningen,那就应该已经熟悉相关的基础知识了。否则,你应该快速的浏览一下Leiningen的GitHub主页3,在那儿你能找到如何安装,以及其基本用法的说明。现在还不用急着去学习所有这一切,因为本书会指引你轻松地掌握那些必须的命令。

在阅读本书期间,请使用与本书示例代码匹配的Clojure版本。读完本书后,你就可以按照“自行构建Clojure”中的说明,构建一个最新鲜的Clojure版本。

自行构建Clojure

你可能希望从源码构建Clojure,以获得最新的特性和bug修复。这里是具体做法。

git clone git://github.com/clojure/clojure.git
cd clojure
mvn package

本书的示例代码会经常更新,与匹配Clojure当前最新的开发版本。请检查示例代码目录中的README文件,里面有最近一次测试通过时,对应的Clojure修订版本号。
请参阅前言中“下载示例代码”中说明,下载本书的示例代码。当你下载了示例代码后,你还需要使用Leiningen获取它们的依赖项。请在示例代码的根目录下执行。

lein deps

那些依赖项将被下载到本地并放置在适当的位置。为了测试安装是否正确,你可以进入到放置示例代码的目录,并启动一个Clojure的REPL(读取-求值-打印循环)。Leiningen包含了一个启动REPL的脚本,它可以连同依赖项一起加载Clojure,本书后面部分会用到那些依赖项。

lein repl

当你成功的启动了REPL,它将会显示“user=>”对你进行提示。

Clojure
user=>

现在,你已经为“Hello World.”做好了准备。

1.2.1 使用REPL
来看看如何使用REPL,让我们创建几个“Hello World”的变体。首先,在REPL提示符下键入(println "hello world")。

user=> (println "hello world")
->hello world

第二行的“hello world”,就是REPL针对你提交的请求,产生的控制台输出。

接下来,将你的“Hello World”封装成一个函数,让它可以通过名字向人问好。

(defn hello [name](str "Hello, " name))
-> #'user/hello

我们来分析一下。

defn定义了一个函数。
hello是这个函数的名称。
hello函数接受一个参数name。
str是一个函数调用,把由任意参数组成的列表连接为一个字符串。
defn、hello、name和str都是符号(symbols),代表了它们各自涉及事物的名称。在第2.1.2小节“符号”中有关于合法符号的定义。
再看看这行代码的返回值:#'user/hello。前缀#'表示这个函数是用一个Clojure变量(var)来保存的,其中user是这个函数所在的命名空间(namespace)(就像Java的默认包一样,user是REPL的默认命名空间)。你现在还不必为变量和名字空间担忧,第2.4节“变量、绑定和命名空间”里有关于它们的讨论。

你现在可以调用hello函数,并传入你的名字了。

user=> (hello "Stu")
-> "Hello, Stu"

如果你发现REPL的状态令你倍感困惑,最简单的解决办法就是直接关闭这个REPL(Windows下使用CTRL+C,*nix下则是CTRL+D),然后再另外启动一个。

1.2.2 特殊变量
REPL包括几个有用的特殊变量。当你使用REPL时,最近三次求值结果的描述被分别存储在特殊变量1、2和*3中。这使得进行迭代变的非常容易。下面,让我们向几个不同的名字问声好。

user=> (hello "Stu")
-> "Hello, Stu"
user=> (hello "Clojure")
-> "Hello, Clojure"

现在,你可以使用那几个特殊变量,把最近的几个工作成果组合起来。

(str *1 " and " *2)
-> "Hello, Clojure and Hello, Stu"

如果你在使用REPL的过程中犯了错,你会看到一个Java异常。出于简洁方面的考虑,细节往往被省略了。例如,除以零是不允许的。

user=> (/ 1 0)
-> ArithmeticException Divide by zero clojure.lang.Numbers.divide

这是个显而易见的问题,但有些时候问题会更加微妙,这时你就需要获得更详细的堆栈跟踪(stack trace)信息了。最后一个异常被保存在特殊变量*e中。由于Clojure异常就是Java异常,所以你能使用pst函数4(print stacktrace)得到堆栈跟踪信息。

user=> (pst)
-> ArithmeticException Divide by zero
| clojure.lang.Numbers.divide
| sun.reflect.NativeMethodAccessorImpl.invoke0
| sun.reflect.NativeMethodAccessorImpl.invoke
| sun.reflect.DelegatingMethodAccessorImpl.invoke
| java.lang.reflect.Method.invoke
| clojure.lang.Reflector.invokeMatchingMethod
| clojure.lang.Reflector.invokeStaticMethod
| user/eval1677
| clojure.lang.Compiler.eval
| clojure.lang.Compiler.eval
| clojure.core/eval

更多与Java互操作方面的内容,参见第9章“极尽Java之所能”。如果你的代码块实在太大,不便于在REPL中逐行敲入,不妨将代码保存到一个文件中,然后通过REPL,使用绝对路径或是相对路径(相对于启动REPL的路径)来加载这个文件。

; 保存一些东西到temp.clj中, 然后执行...

user=> (load-file "temp.clj")

REPL是一个美妙的场所,在这里你可以尝试各种想法并立即获得反馈。为达到最佳效果,阅读本书时,请务必保持随时都开启着REPL。

1.2.3 添加共享状态
上一节中的hello函数是“纯粹的”,也就是说,它不会产生任何副作用。纯函数易于开发、测试,并易于理解,你应该优先选择它们来处理任务。

可是,大多数程序拥有共享状态,并且需要使用非纯粹的函数来管理这些共享状态。让我们对hello函数进行扩展,使其能够追踪过往访客的足迹。首先,你需要一种数据结构来追踪访客。集合就非常合适。

{}
-> {}

{}是空集合的字面表示法。接下来,你需要conj函数。

(conj coll item)

conj是conjoin(连接)的缩写,它会新建一个含有新增项的集合。将元素连接到集合,就好像是创建了一个新的集合。

(conj #{} "Stu")
-> {"Stu"}

现在你可以创建新的集合了,但你还需要某种方法来对当前访客的集合保持跟踪。为此,Clojure提供了几种引用类型。最基本的引用类型是原子。

(atom initial-state)

你可以使用def来为你的原子命名。

(def symbol initial-value?)

def有点像defn,但更为通用。Def既能定义函数,又能定义数据。下面使用atom创建一个原子,并用def将这个原子绑定到名称visitors上。

(def visitors (atom #{}))
-> #’user/visitors

要更新一个引用,你需要使用诸如swap!这样的函数。

(swap! r update-fn & args)

swap!会对拿引用r去调用update-fn,并根据需要传递其他可选的参数。下面试一下用conj作为更新函数,把一个访客swap!进入到访客集合中。

(swap! visitors conj "Stu")
-> #{"Stu"}

原子只是Clojure的几种引用类型之一。选择恰当的引用类型时,需要格外小心仔细(相关讨论参见第5章“状态”)。

你可以在任何时候使用deref或者它的缩写@号来提取引用内部的值。

(deref visitors)
-> #{"Stu"}
@visitors
-> #{"Stu"}

现在,是时候创建这个更加复杂的新版hello了。

src/examples/introduction.clj
(defn hello
 "Writes hello message to *out*. Calls you by username.
  Knows if you have been here before."
 [username]
 (swap! visitors conj username)
 (str "Hello, " username))

下一步,检查一下看能否在内存中正确地追踪了。

(hello "Rich")
-> "Hello, Rich"
@visitors
-> #{"Aaron" "Stu" "Rich"}

你的访客列表十有八九与此处显示的不同。这就是状态捣的乱!结果是否会有差别,取决于事情何时发生。你还可以据此推论出一个函数是否管理着本地信息。对状态进行推断,需要对其演变历史有着充分的认识。

只要可能,就应该极力避免状态。但是当你确实需要它的时候,通过使用诸如原子这样的引用类型,就能让状态保持完整以及可控。原子(和所有其他的Clojure引用类型)对于多个线程和多个处理器都是安全的。更棒的是,获得这种安全性无需借助声名狼藉的锁定机制,那实在是太让人抓狂了。

至此,你应该已经能很舒畅的在REPL中录入那些较短的代码了。其实那些较长的代码也没有太多不同,你同样可以在REPL中加载并运行大量有成百上千行代码的Clojure库。下面让我们来探索一下吧。

1 http://www.oracle.com/technetwork/java/javase/downloads/index.html
2 http://github.com/technomancy/leiningen
3 http://github.com/technomancy/leiningen
4 pst函数仅适用于Clojure1.3.0及更高版本。

相关文章
|
Java PHP 开发工具
编程语言Clojure入门
在众多的编程语言中,不少开发人员熟悉Java、C#、PHP等。但是很早以前,也有一些小众的语言,比如Lisp语言,它是一种适用于符号处理和自动推理的编程语言,内部使用表结构来表达非数值计算。而Clojure语言是在JVM上实现的Lisp风格的语言,语法与Lisp类似,且可以和Java语言进行互操作
1139 0
编程语言Clojure入门
|
6月前
|
安全 Java 编译器
Scala语言入门:初学者的基础语法指南
作为一种在Java虚拟机(JVM)上运行的静态类型编程语言,Scala结合了面向对象和函数式编程的特性,使它既有强大的表达力又具备优秀的型态控制
27 0
|
Shell BI 测试技术
Haskell 编程入门
在过去的几个月里,学习Haskell让我觉得非常快乐,但是入门的过程并没有我原先想象的那么简单。我非常幸运地在一个正确的地方工作,并且因此能够在Facebook参加Bryan O'Sullivan的Haskell课程。在Try Haskell上玩了一段时间后,最终你就会想要在自己的电脑上安装GHC了。
173 0
Haskell 编程入门
|
Kotlin Java Android开发
带你读《Kotlin核心编程》之三:面向对象
本书不是一本简单介绍Kotlin语法应用的图书,而是一部专注于帮助读者深入理解Kotlin的设计理念,指导读者实现Kotlin高层次开发的实战型著作。书中深入介绍了Kotlin的核心语言特性、设计模式、函数式编程、异步开发等内容,并以Android和Web两个平台为背景,演示了Kotlin的实战应用。
|
Java
《Clojure程序设计》——导读
本节书摘来自异步社区《Clojure程序设计》一书中的导读,作者 【美】Stuart Halloway , Aaron Bedra,更多章节内容可以访问云栖社区“异步社区”公众号查看
2006 0