- Clojure 教程
- Clojure - 首页
- Clojure - 概述
- Clojure - 环境
- Clojure - 基本语法
- Clojure - REPL
- Clojure - 数据类型
- Clojure - 变量
- Clojure - 运算符
- Clojure - 循环
- Clojure - 决策
- Clojure - 函数
- Clojure - 数字
- Clojure - 递归
- Clojure - 文件 I/O
- Clojure - 字符串
- Clojure - 列表
- Clojure - 集合
- Clojure - 向量
- Clojure - 映射
- Clojure - 命名空间
- Clojure - 异常处理
- Clojure - 序列
- Clojure - 正则表达式
- Clojure - 断言
- Clojure - 解构
- Clojure - 日期和时间
- Clojure - 原子
- Clojure - 元数据
- Clojure - StructMaps
- Clojure - 代理
- Clojure - 观察者
- Clojure - 宏
- Clojure - 引用值
- Clojure - 数据库
- Clojure - Java 接口
- Clojure - 并发编程
- Clojure - 应用
- Clojure - 自动化测试
- Clojure - 库
- Clojure 有用资源
- Clojure 快速指南
- Clojure - 有用资源
- Clojure - 讨论
Clojure 快速指南
Clojure - 概述
Clojure 是一种高级的、动态的函数式编程语言。Clojure 基于 LISP 编程语言设计,并具有编译器,使其能够在 Java 和 .Net 运行时环境上运行。
在我们讨论 Clojure 之前,让我们快速了解一下 LISP 编程语言。LISP 具有极小的语言核心、几乎没有语法以及强大的宏功能。凭借这些特性,您可以根据自己的设计来调整 LISP,而不是反过来。LISP 历史悠久,可追溯到 1958 年。
Common LISP 读取表达式、对其求值,然后打印结果。例如,如果您想计算 4+6 这个简单数学表达式的值,则输入:
USER(1) (+ 4 6)
Clojure 作为一种编程语言,具有以下高级关键目标:
它基于 LISP 编程语言,这使得它的代码语句比传统的编程语言更简洁。
它是一种函数式编程语言。
它专注于不变性,这基本上是指不应修改已创建的对象。
它可以为程序员管理应用程序的状态。
它支持并发。
它兼容现有的编程语言。例如,Clojure 可以利用整个 Java 生态系统,通过 JVM 来管理代码的运行。
Clojure 的官方网站是 https://clojure.org/
Clojure - 环境
有多种方法可以将 Clojure 作为编程语言来使用。我们将探讨两种使用 Clojure 编程的方法。
Leiningen − Leiningen 是一个创建、构建和自动化 Clojure 项目的重要工具。
Eclipse 插件 − Eclipse 上有一个名为 CounterClockwise 的插件,可用于在 Eclipse IDE 中进行 Clojure 开发。
Leiningen 安装
在继续安装之前,请确保满足以下系统要求。
系统要求
JDK | JDK 1.7 或更高版本 |
---|---|
内存 | 2 GB RAM(推荐) |
步骤 1 − 下载二进制安装程序。访问链接 http://leiningen-wininstaller 获取 Windows 安装程序。点击选项开始下载 Groovy 安装程序。
步骤 2 − 启动安装程序并点击“下一步”按钮。
步骤 3 − 指定安装位置并点击“下一步”按钮。
步骤 4 − 安装程序将检测现有 Java 安装的位置。点击“下一步”按钮继续。
步骤 5 − 点击“安装”按钮开始安装。
安装完成后,它将为您提供打开 Clojure REPL 的选项,这是一个可用于创建和测试 Clojure 程序的环境。
Eclipse 安装
在继续安装之前,请确保满足以下系统要求。
系统要求
JDK | JDK 1.7 或更高版本 |
---|---|
Eclipse | Eclipse 4.5 (Mars) |
步骤 1 − 打开 Eclipse 并点击菜单项。点击帮助 → Eclipse 市场。
步骤 2 − 在出现的对话框中输入关键字 Clojure 并点击“Go”按钮。将出现 CounterClockwise 选项,点击“安装”按钮开始安装此插件。
步骤 3 − 在下一个对话框中,点击“确认”按钮开始安装。
步骤 4 − 在下一个对话框中,您将被要求接受许可协议。接受许可协议并点击“完成”按钮继续安装。
安装将开始,完成后,它将提示您重新启动 Eclipse。
重新启动 Eclipse 后,您将在 Eclipse 中看到创建新 Clojure 项目的选项。
Clojure - 基本语法
为了理解 Clojure 的基本语法,让我们首先来看一个简单的 Hello World 程序。
完整的 Hello World 程序
编写一个完整的 Clojure 程序来输出“Hello world”。以下是一个示例。
示例
(ns clojure.examples.hello (:gen-class)) (defn hello-world [] (println "Hello World")) (hello-world)
关于上述程序,需要注意以下几点:
程序将写入名为 main.clj 的文件中。“clj”扩展名是 Clojure 代码文件的扩展名。在上面的示例中,文件名是 main.clj。
“defn”关键字用于定义函数。我们将在另一章详细介绍函数。但现在,请知道我们正在创建一个名为 helloworld 的函数,其中包含我们的主要 Clojure 代码。
在我们的 Clojure 代码中,我们使用“println”语句将“Hello World”打印到控制台输出。
然后我们调用 hello-world 函数,该函数依次运行“println”语句。
上述程序产生以下输出。
输出
Hello World
语句的一般形式
任何语句的一般形式都需要在大括号中求值,如下例所示。
(+ 1 2)
在上面的示例中,整个表达式都用大括号括起来。上述语句的输出是 3。“+”运算符在 Clojure 中像函数一样,用于数字的加法。1 和 2 的值被称为函数参数。
让我们考虑另一个示例。在这个示例中,“str”是用于连接两个字符串的运算符。“Hello”和“World”用作参数。
(str "Hello" "World")
示例
如果我们将上述两个语句组合起来编写一个程序,它将如下所示。
(ns clojure.examples.hello (:gen-class)) (defn Example [] (println (str "Hello World")) (println (+ 1 2))) (Example)
输出
上述程序产生以下输出。
Hello World 3
命名空间
命名空间用于定义 Clojure 中定义的模块之间的逻辑边界。
当前命名空间
这定义了当前 Clojure 代码所在的当前命名空间。
语法
*ns*
示例
在 REPL 命令窗口中运行以下命令。
*ns*
输出
当我们运行上述命令时,输出将取决于当前命名空间。以下是一个输出示例。Clojure 代码的命名空间是:
clojure.examples.hello (ns clojure.examples.hello (:gen-class)) (defn Example [] (println (str "Hello World")) (println (+ 1 2))) (Example)
Clojure 中的 require 语句
Clojure 代码打包在库中。每个 Clojure 库都属于一个命名空间,这类似于 Java 包。您可以使用“require”语句加载 Clojure 库。
语法
(require quoted-namespace-symbol)
示例
以下是此语句用法的示例。
(ns clojure.examples.hello (:gen-class)) (require ‘clojure.java.io’) (defn Example [] (.exists (file "Example.txt"))) (Example)
在上面的代码中,我们使用“require”关键字导入命名空间 clojure.java.io,其中包含输入/输出功能所需的所有函数。由于我们没有所需的库,因此我们可以在上面的代码中使用“file”函数。
Clojure 中的注释
注释用于记录代码。单行注释由在该行的任何位置使用 ;; 来标识。以下是一个示例。
示例
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (println "Hello World")) (Example)
分隔符
在 Clojure 中,可以使用弯括号或方括号来分割或分隔语句。
示例
以下是两个示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (println (+ 1 2 3))) (Example)
输出
上述程序产生以下输出。
6
示例
以下是另一个示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (println [+ 1 2 3])) (Example)
输出
上述程序产生以下输出。
[#object[clojure.core$_PLUS_ 0x10f163b "clojure.core$_PLUS_@10f163b"] 1 2 3]
空格
可以在 Clojure 中使用空格来分割语句的不同组件以提高清晰度。这可以通过逗号 (,) 运算符来实现。
例如,以下两个语句是等效的,并且两个语句的输出都将是 15。
(+ 1 2 3 4 5) (+ 1, 2, 3, 4, 5)
虽然 Clojure 忽略逗号,但有时它会使用逗号来使程序员更容易阅读。
例如,如果您有一个如下所示的哈希映射 (def a-map {:a 1 :b 2 :c 3}) 并在 REPL 窗口中请求其值,Clojure 将输出为 {:a 1, :b 2, :c 3}。
结果更容易阅读,尤其是在查看大量数据时。
符号
在 Clojure 中,符号相当于其他编程语言中的标识符。但与其他编程语言不同,编译器将符号视为实际的字符串值。由于符号是一个值,因此符号可以像任何其他对象一样存储在集合中、作为参数传递给函数等。
符号只能包含字母数字字符和“* + ! / . : - _ ?”,但不能以数字或冒号开头。
以下是符号的有效示例。
tutorial-point! TUTORIAL +tutorial+
Clojure 项目结构
最后,让我们谈谈 Clojure 项目的典型项目结构。由于 Clojure 代码运行在 Java 虚拟机上,因此 Clojure 中的大部分项目结构与您在 Java 项目中找到的结构类似。以下是 Eclipse 中 Clojure 项目的示例项目结构快照。
关于上述程序结构,需要注意以下关键事项:
demo_1 − 这是放置 Clojure 代码文件的包。
core.clj − 这是主要的 Clojure 代码文件,其中将包含 Clojure 应用程序的代码。
Leiningen 文件夹包含诸如 clojure-1.6.0.jar 之类运行任何基于 Clojure 的应用程序所需的文件。
pom.properties 文件将包含诸如 Clojure 项目的 groupId、artifactId 和版本之类的信息。
project.clj 文件包含有关 Clojure 应用程序本身的信息。以下是项目文件内容的示例。
(defproject demo-1 "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.6.0"]])
Clojure - REPL
REPL(read-eval-print loop,读取-求值-打印循环)是用于试验 Clojure 代码的工具。它允许您与正在运行的程序交互并快速尝试是否一切按预期进行。它通过向您显示一个提示来实现此目的,您可以在其中输入代码。然后,它读取您的输入、对其求值、打印结果并循环,再次向您显示提示。
此过程能够实现大多数其他语言中无法实现的快速反馈循环。
启动 REPL 会话
可以通过在命令行中键入以下命令在 Leiningen 中启动 REPL 会话。
lein repl
这将启动以下 REPL 窗口。
然后根据需要开始在 REPL 窗口中评估 Clojure 命令。
要在 Eclipse 中启动 REPL 会话,请点击菜单选项,转到“以...方式运行”→“Clojure 应用程序”。
这将在一个单独的窗口中启动一个新的 REPL 会话以及控制台输出。
从概念上讲,REPL 类似于安全 shell (SSH)。就像您可以使用 SSH 与远程服务器交互一样,Clojure REPL 允许您与正在运行的 Clojure 进程交互。此功能非常强大,因为您甚至可以将 REPL 附加到实时生产应用程序并修改正在运行的程序。
REPL 中的特殊变量
REPL 包含一些有用的变量,其中广泛使用的是特殊变量 *1、*2 和 *3。这些用于评估最近三个表达式的结果。
以下示例显示了如何使用这些变量。
user => "Hello" Hello user => "World" World user => (str *2 *1) HelloWorld
在上述示例中,前两个字符串分别作为“Hello”和“World”发送到 REPL 输出窗口。然后使用 *2 和 *1 变量来调出最后两个计算的表达式。
Clojure - 数据类型
Clojure 提供了各种内置数据类型。
内置数据类型
以下是 Clojure 中定义的数据类型列表。
整数 −以下是 Clojure 中可用的整数表示。
十进制整数(短整型、长整型和整型) −这些用于表示整数。例如,1234。
八进制数 −这些用于表示八进制表示的数字。例如,012。
十六进制数 −这些用于表示十六进制表示的数字。例如,0xff。
基数 −这些用于表示基数表示的数字。例如,2r1111,其中基数是在 2 到 36(包括 2 和 36)之间的整数。
浮点数
默认情况下用于表示 32 位浮点数。例如,12.34。
另一种表示法是科学计数法。例如,1.35e-12。
字符 −这定义了单个字符字面量。字符用反斜杠符号定义。例如,/e。
布尔值 −这表示布尔值,可以是真或假。
字符串 −这些是文本字面量,以字符链的形式表示。例如,“Hello World”。
Nil −这用于表示 Clojure 中的 NULL 值。
原子 (Atom) −原子提供了一种管理共享的、同步的、独立的状态的方法。它们与 refs 和 vars 一样是一种引用类型。
绑定值
由于 Clojure 中的所有数据类型都继承自 Java,因此绑定值与 Java 编程语言中的相同。下表显示了数值和十进制字面量的最大允许值。
字面量 | 范围 |
---|---|
短整型 (Short) | -32,768 到 32,767 |
整型 (int) | -2,147,483,648 到 2,147,483,647 |
长整型 (long) | -9,223,372,036,854,775,808 到 +9,223,372,036,854,775,807 |
单精度浮点数 (float) | 1.40129846432481707e-45 到 3.40282346638528860e+38 |
双精度浮点数 (double) | 4.94065645841246544e-324d 到 1.79769313486231570e+308d |
类数值类型
除了原始类型外,还允许使用以下对象类型(有时称为包装器类型)。
名称 |
---|
java.lang.Byte |
java.lang.Short |
java.lang.Integer |
java.lang.Long |
java.lang.Float |
java.lang.Double |
示例
下面的程序显示了一个整合的 Clojure 代码,用于演示 Clojure 中的数据类型。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] ;; The below code declares a integer variable (def x 1) ;; The below code declares a float variable (def y 1.25) ;; The below code declares a string variable (def str1 "Hello") (println x) (println y) (println str1)) (Example)
输出
上述程序产生以下输出。
1 1.25 Hello
Clojure - 变量
在 Clojure 中,变量由“def”关键字定义。它有点不同,其中变量的概念更多地与绑定有关。在 Clojure 中,值绑定到变量。在 Clojure 中需要注意的一点是,变量是不可变的,这意味着要更改变量的值,需要销毁它并重新创建它。
以下是 Clojure 中的基本变量类型。
短整型 (short) −这用于表示短整数。例如,10。
整型 (int) −这用于表示整数。例如,1234。
长整型 (long) −这用于表示长整数。例如,10000090。
单精度浮点数 (float) −这用于表示 32 位浮点数。例如,12.34。
字符 (char) −这定义了单个字符字面量。例如,‘/a’。
布尔值 −这表示布尔值,可以是真或假。
字符串 −这些是文本字面量,以字符链的形式表示。例如,“Hello World”。
变量声明
以下是定义变量的通用语法。
语法
(def var-name var-value)
其中“var-name”是变量的名称,“var-value”是绑定到变量的值。
示例
以下是一个变量声明的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] ;; The below code declares a integer variable (def x 1) ;; The below code declares a float variable (def y 1.25) ;; The below code declares a string variable (def str1 "Hello") ;; The below code declares a boolean variable (def status true)) (Example)
变量命名
变量名可以由字母、数字和下划线组成。它必须以字母或下划线开头。大小写字母是不同的,因为 Clojure 与 Java 一样是一种区分大小写的编程语言。
示例
以下是 Clojure 中一些变量命名的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] ;; The below code declares a Boolean variable with the name of status (def status true) ;; The below code declares a Boolean variable with the name of STATUS (def STATUS false) ;; The below code declares a variable with an underscore character. (def _num1 2)) (Example)
注意 −在上述语句中,由于大小写敏感性,status 和 STATUS 是 Clojure 中定义的两个不同的变量。
以上示例显示了如何使用下划线字符定义变量。
打印变量
由于 Clojure 使用 JVM 环境,您也可以使用“println”函数。以下示例显示了如何实现这一点。
示例
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] ;; The below code declares a integer variable (def x 1) ;; The below code declares a float variable (def y 1.25) ;; The below code declares a string variable (def str1 "Hello") (println x) (println y) (println str1)) (Example)
输出
上述程序产生以下输出。
1 1.25 Hello
Clojure - 运算符
运算符是一个符号,它告诉编译器执行特定的数学或逻辑操作。
Clojure 具有以下类型的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 按位运算符
注意 −在 Clojure 中,运算符和操作数按以下语法方式工作。
语法
(operator operand1 operand2 operandn)
例如:
示例
(+ 1 2)
以上示例对数字 1 和 2 执行算术运算。
算术运算符
Clojure 语言支持与任何语言相同的普通算术运算符。以下是 Clojure 中可用的算术运算符。
运算符 | 描述 | 示例 |
---|---|---|
+ | 两个操作数的加法 | (+ 1 2) 将得到 3 |
− | 从第一个操作数中减去第二个操作数 | (- 2 1) 将得到 1 |
* | 两个操作数的乘法 | (* 2 2) 将得到 4 |
/ | 分子除以分母 | (float (/ 3 2)) 将得到 1.5 |
inc | 增量运算符,用于将操作数的值增加 1 | inc 5 将得到 6 |
dec | 增量运算符,用于将操作数的值减少 1 | dec 5 将得到 4 |
max | 返回其参数中最大的一个 | max 1 2 3 将返回 3 |
min | 返回其参数中最小的一个 | min 1 2 3 将返回 1 |
rem | 将第一个数字除以第二个数字的余数 | rem 3 2 将得到 1 |
关系运算符
关系运算符允许比较对象。以下是 Clojure 中可用的关系运算符。
运算符 | 描述 | 示例 |
---|---|---|
= | 测试两个对象之间的相等性 | (= 2 2) 将得到 true |
not= | 测试两个对象之间的差异 | (not= 3 2) 将得到 true |
< | 检查左侧对象是否小于右侧操作数 | (< 2 3) 将得到 true |
<= | 检查左侧对象是否小于或等于右侧操作数 | (<= 2 3) 将得到 true |
> | 检查左侧对象是否大于右侧操作数 | (> 3 2) 将得到 true |
>= | 检查左侧对象是否大于或等于右侧操作数 | (>= 3 2) 将得到 true |
逻辑运算符
逻辑运算符用于评估布尔表达式。以下是 Groovy 中可用的逻辑运算符。
运算符 | 描述 | 示例 |
---|---|---|
and | 这是逻辑“与”运算符 | (or true true) 将得到 true |
or | 这是逻辑“或”运算符 | (and true false) 将得到 false |
not | 这是逻辑“非”运算符 | (not false) 将得到 true |
以下代码片段显示了如何使用各种运算符。
按位运算符
Clojure 提供四个按位运算符。以下是 Clojure 中可用的按位运算符。
序号 | 运算符和描述 |
---|---|
1 |
bit-and 这是按位“与”运算符 |
2 |
bit-or 这是按位“或”运算符 |
3 |
bit-xor 这是按位“异或”或“异或”运算符 |
4 |
bit-not 这是按位取反运算符 |
以下是展示这些运算符的真值表。
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算符优先级
与一般的 LISP 一样,无需担心运算符优先级。这是 S 表达式和前缀表示法的优点之一。所有函数都从左到右、从内到外进行计算。Clojure 中的运算符只是函数,并且所有内容都完全用括号括起来。
Clojure - 循环
到目前为止,我们已经看到了按顺序依次执行的语句。此外,Clojure 中还提供了语句来改变程序逻辑中的控制流。然后,它们被分类为控制流语句,我们将在后面详细介绍。
序号 | 循环和描述 |
---|---|
1 | While 语句
'while' 语句通过首先计算条件表达式(布尔值)来执行,如果结果为真,则执行 while 循环中的语句。 |
2 | Doseq 语句
'doseq' 语句类似于在许多其他编程语言中找到的 'for each' 语句。doseq 语句基本上用于迭代序列。 |
3 | Dotimes 语句
'dotimes' 语句用于执行 'x' 次语句。 |
4 | Loop 语句
loop 特殊形式不像'for'循环。loop 的用法与 let 绑定相同。但是,loop 设置了一个递归点。 |
Clojure - 决策
决策结构要求程序员指定一个或多个条件供程序评估或测试,以及如果确定条件为真则要执行的语句,以及可选地,如果确定条件为假则要执行的其他语句。
序号 | 方法和描述 |
---|---|
1 | If 语句
在 Clojure 中,条件是一个表达式,它将表达式计算为真或假。'如果'条件为真,则执行 statement#1,否则执行 statement#2。 |
2 | If/do 表达式
Clojure 中的'if-do'表达式用于允许为'if'语句的每个分支执行多个表达式。 |
3 | 嵌套 If 语句
多个'if'语句相互嵌套。 |
4 | Case 语句
Clojure 提供了'case'语句,它类似于 Java 编程语言中提供的'switch'语句。 |
5 | Cond 语句
Clojure 提供了另一个评估语句,称为'cond'语句。此语句采用一组测试/表达式对。 |
Clojure - 函数
Clojure 是一种函数式编程语言,因此您会期望看到很多关于 Clojure 中函数如何工作的重点。本章介绍了在 Clojure 中可以使用函数执行的所有操作。
序号 | 函数和描述 |
---|---|
1 | 定义函数
函数是使用'defn'宏定义的。 |
2 | 匿名函数
匿名函数是没有与其关联的名称的函数。 |
3 | 具有多个参数的函数
Clojure 函数可以使用零个或多个参数定义。传递给函数的值称为参数,参数可以是任何类型。 |
4 | 可变参数函数
Clojure 提供了 'case' 语句,它类似于 Java 编程语言中提供的 'switch' 语句。 |
5 | 高阶函数
高阶函数 (HOF) 是将其他函数作为参数的函数。HOF 是一种重要的函数式编程技术,在 Clojure 中非常常用。 |
Clojure - 数字
Clojure 中的数字数据类型派生自 Java 类。
Clojure 支持整数和浮点数。
整数是不包含小数部分的值。
浮点数是包含小数部分的十进制值。
以下是 Clojure 中数字的示例。
(def x 5) (def y 5.25)
其中 ‘x’ 的类型为 **整数**,‘y’ 的类型为 **浮点数**。
在 Java 中,以下类与 Clojure 中定义的数字相关联。
要实际查看 Clojure 中的数字是否派生自 Java 类,请使用以下程序查看使用 ‘def’ 命令时分配的数字类型。
示例
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (def x 5) (def y 5.25) (println (type x)) (println (type y))) (Example)
**‘type’** 命令用于输出与分配给变量的值关联的类。
输出
上述代码将产生以下输出。
Java.lang.long Java.lang.double
数字测试
以下测试函数可用于数字。
序号 | 数字及描述 |
---|---|
1 | zero?
如果数字为零,则返回 true,否则返回 false。 |
2 | pos?
如果数字大于零,则返回 true,否则返回 false。 |
3 | neg?
如果数字小于零,则返回 true,否则返回 false。 |
4 | even?
如果数字为偶数,则返回 true;如果数字不是整数,则抛出异常。 |
5 | odd?
如果数字为奇数,则返回 true;如果数字不是整数,则抛出异常。 |
6 | number?
如果数字确实是数字,则返回 true。 |
7 | integer?
如果数字是整数,则返回 true。 |
8 | float?
如果数字是浮点数,则返回 true。 |
Clojure - 递归
我们在之前的主题中已经看到了 recur 语句,虽然 ‘for’ 循环有点像循环,但 **recur** 是 Clojure 中真正的循环。
如果您有编程背景,您可能听说过尾递归,这是函数式语言的一个主要特性。这个 recur 特殊形式实现了尾递归。正如“尾递归”这个词所指出的那样,必须在尾部位置调用 recur。换句话说,recur 必须是最后要计算的东西。
recur 语句最简单的例子是在 ‘for’ 循环中使用。在下面的例子中,recur 语句用于更改变量 ‘i’ 的值,并将变量的值反馈给循环表达式。
示例
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (loop [i 0] (when (< i 5) (println i) (recur (inc i))))) (Example)
输出
上述程序产生以下输出。
0 1 2 3 4
Clojure - 文件 I/O
Clojure 在处理 I/O 时提供了一些辅助方法。它提供了更简单的类来为文件提供以下功能。
- 读取文件
- 写入文件
- 查看文件是文件还是目录
让我们探索 Clojure 提供的一些文件操作。
将文件的全部内容读取为单个字符串
如果您想将文件的全部内容作为字符串获取,可以使用 **clojure.core.slurp** 方法。slurp 命令打开文件上的读取器并读取其所有内容,返回一个字符串。
以下是如何执行此操作的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (def string1 (slurp "Example.txt")) (println string1)) (Example)
如果文件包含以下几行,它们将被打印为:
line : Example1 line : Example2
逐行读取文件的全部内容
如果您想逐行将文件的全部内容作为字符串获取,可以使用 **clojure.java.io/reader** 方法。clojure.java.io/reader 类创建一个读取器缓冲区,用于读取文件的每一行。
以下是一个显示如何执行此操作的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (with-open [rdr (clojure.java.io/reader "Example.txt")] (reduce conj [] (line-seq rdr)))) (Example)
如果文件包含以下几行,它们将被打印为:
line : Example1 line : Example2
输出将显示为:
["line : Example1" "line : Example2"]
写入文件
如果您想写入文件,可以使用 **clojure.core.spit** 命令将整个字符串写入文件。spit 命令与 slurp 方法相反。此方法将文件打开为写入器,写入内容,然后关闭文件。
以下是一个示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (spit "Example.txt" "This is a string"))
在上面的示例中,如果您查看 Example.txt 文件的内容,您将看到“This is a string”的内容。
逐行写入文件
如果您想逐行写入文件,可以使用 **clojure.java.io.writer** 类。clojure.java.io.writer 类用于创建一个写入器流,其中数据字节被馈送到流中,然后馈送到文件中。
以下是一个显示如何使用 spit 命令的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (with-open [w (clojure.java.io/writer "Example.txt" :append true)] (.write w (str "hello" "world")))) (Example)
执行上述代码后,"hello world" 行将出现在 Example.txt 文件中。append:true 选项用于将数据追加到文件。如果未指定此选项,则每次写入数据时都会覆盖文件。
检查文件是否存在
要检查文件是否存在,可以使用 **clojure.java.io.file** 类来检查文件是否存在。以下是一个显示如何实现此目的的示例。
(ns clojure.examples.hello (:gen-class)) ;; This program displays Hello World (defn Example [] (println (.exists (clojure.java.io/file "Example.txt")))) (Example)
如果 Example.txt 文件存在,则输出为 true。
从控制台读取
要从控制台读取数据,可以使用 **read-line** 语句。以下是一个显示如何使用它的示例。
如果您在 REPL 窗口中输入 (read-line) 命令,您将有机会在控制台窗口中输入一些输入。
user->(read-line) Hello World
上述代码将产生以下输出。
“Hello World”
Clojure - 字符串
**字符串**字面量在 Clojure 中通过用引号括起字符串文本来构造。Clojure 中的字符串需要使用双引号构造,例如“Hello World”。
示例
以下是 Clojure 中字符串用法的示例。
(ns clojure.examples.hello (:gen-class)) (defn hello-world [] (println "Hello World") (println "This is a demo application")) (hello-world)
输出
上述程序产生以下输出。
Hello World This is a demo application
基本的字符串操作
Clojure 有许多可以对字符串执行的操作。以下是这些操作。
序号 | 字符串操作及描述 |
---|---|
1 | str
可以使用简单的 str 函数进行字符串连接。 |
2 | format
可以使用简单的 format 函数对字符串进行格式化。format 函数使用 **java.lang.String.format** 格式化字符串。 |
3 | count
返回字符串中的字符数。 |
4 | subs
返回字符串 ‘s’ 从开始位置(包含)到结束位置(默认为字符串长度,不包含)的子字符串。 |
5 | compare
当 ‘x’ 在逻辑上“小于”、“等于”或“大于”‘y’ 时,返回负数、零或正数。 |
6 | lower-case
将字符串转换为全小写。 |
7 | upper-case
将字符串转换为全大写。 |
8 | join
返回集合中所有元素的字符串,这些元素由 (seq collection) 返回,并用可选的分隔符分隔。 |
9 | split
根据正则表达式分割字符串。 |
10 | split-lines
基于转义字符 \n 或 \r\n 分割字符串。 |
11 | reverse
反转字符串中的字符。 |
12 | replace
将字符串中所有匹配项的实例替换为替换字符串。 |
13 | trim
删除字符串两端的空格。 |
14 | triml
删除字符串左侧的空格。 |
15 | trimr
删除字符串右侧的空格。 |
Clojure - 列表
**列表**是一种用于存储数据项集合的结构。在 Clojure 中,List 实现 **ISeq** 接口。列表在 Clojure 中是使用 list 函数创建的。
示例
以下是 Clojure 中创建数字列表的示例。
(ns clojure.examples.example (:gen-class)) (defn example [] (println (list 1 2 3 4))) (example)
输出
上述代码产生以下输出。
(1 2 3 4)
以下是 Clojure 中创建字符列表的示例。
(ns clojure.examples.example (:gen-class)) (defn example [] (println (list 'a 'b 'c 'd))) (example)
上述代码产生以下输出。
(a b c d)
以下是 Clojure 中可用的列表方法。
序号 | 列表及描述 |
---|---|
1 | list*
创建一个新的列表,其中包含附加到 rest 的项目,最后一个项目将被视为一个序列。 |
2 | first
此函数返回列表中的第一个项目。 |
3 | nth
此函数返回列表中第 ‘nth’ 个位置的项目。 |
4 | cons
返回一个新的列表,其中一个元素添加到列表的开头。 |
5 | conj
返回一个新的列表,其中列表位于开头,要附加的元素位于结尾。 |
6 | rest
返回列表中第一个项目之后剩余的项目。 |
Clojure - 集合
Clojure 中的**集合**是一组唯一的值。集合是在 Clojure 中借助 set 命令创建的。
示例
以下是 Clojure 中创建集合的示例。
(ns clojure.examples.example (:gen-class)) (defn example [] (println (set '(1 1 2 2)))) (example)
输出
上述代码产生以下输出。
#{1,2}
以下是 Clojure 中可用于集合的方法。
序号 | 集合及描述 |
---|---|
1 | sorted-set
返回一个排序的元素集合。 |
2 | get
返回索引位置的元素。 |
3 | contains?
查找集合是否包含某个元素。 |
4 | conj
将元素附加到集合中并返回新的元素集合。 |
5 | disj
从集合中分离一个元素。 |
6 | union
返回输入集合的并集。 |
7 | difference
返回第一个集合中不包含其余集合元素的集合。 |
8 | intersection
返回输入集合的交集。 |
9 | subset?
集合 1 是否是集合 2 的子集? |
10 | superset?
集合 1 是否是集合 2 的超集? |
Clojure - 向量
**向量**是由连续整数索引的值的集合。向量是使用 Clojure 中的 vector 方法创建的。
示例
以下是 Clojure 中创建向量的示例。
(ns clojure.examples.example (:require [clojure.set :as set]) (:gen-class)) (defn example [] (println (vector 1 2 3))) (example)
输出
上述代码产生以下输出。
[1 2 3]
以下是 Clojure 中可用的方法。
序号 | 向量及描述 |
---|---|
1 | vector-of
创建一个单个基本类型 ‘t’ 的新向量,其中 ‘t’ 是 :int :long :float :double :byte :short :char 或 :boolean 之一。 |
2 | nth
此函数返回向量中第 nth 个位置的项目。 |
3 | get
返回向量中索引位置的元素。 |
4 | conj
将元素附加到向量中并返回新的向量元素集合。 |
5 | pop
对于列表或队列,返回一个不包含第一个项目的新的列表/队列;对于向量,返回一个不包含最后一个项目的新的向量。 |
6 | subvec
从起始和结束索引返回一个子向量。 |
Clojure - 映射
**映射**是一个将键映射到值的集合。提供了两种不同的映射类型 - 散列和排序。**哈希映射**需要正确支持 hashCode 和 equals 的键。**排序映射**需要实现 Comparable 的键,或 Comparator 的实例。
映射可以通过两种方式创建,第一种是通过 hash-map 方法。
创建 - 哈希映射
哈希映射具有典型的键值关系,并通过使用 hash-map 函数创建。
(ns clojure.examples.example (:gen-class)) (defn example [] (def demokeys (hash-map "z" "1" "b" "2" "a" "3")) (println demokeys)) (example)
输出
上述代码产生以下输出。
{z 1, b 2, a 3}
创建 - 排序映射
排序映射具有根据键元素排序其元素的独特特性。以下是一个示例,它显示了如何使用 sorted-map 函数创建排序映射。
(ns clojure.examples.example (:gen-class)) (defn example [] (def demokeys (sorted-map "z" "1" "b" "2" "a" "3")) (println demokeys)) (example)
上述代码产生以下输出。
{a 3, b 2, z 1}
从上面的程序中,您可以清楚地看到映射中的元素是根据键值排序的。以下是可用于映射的方法。
序号 | 映射及描述 |
---|---|
1 | get
返回映射到键的值,如果键不存在,则返回 not-found 或 nil。 |
2 | contains?
查看映射是否包含所需的键。 |
3 | find
返回键的映射条目。 |
4 | keys
返回映射中的键列表。 |
5 | vals
返回映射中的值列表。 |
6 | dissoc
从映射中分离键值条目。 |
7 | merge
将两个映射条目合并为一个映射条目。 |
8 | merge-with
返回一个映射,该映射由连接到第一个映射的其余映射组成。 |
9 | select-keys
返回一个映射,该映射仅包含其键位于 keys 中的映射中的那些条目。 |
10 | rename-keys
将当前 HashMap 中的键重命名为新定义的键。 |
11 | map-invert
反转映射,以便值成为键,反之亦然。 |
Clojure - 命名空间
Clojure 中的**命名空间**用于像在 Java 中一样将类区分到单独的逻辑空间中。考虑以下语句。
(:require [clojure.set :as set])
在上述语句中,‘clojure.set’是一个命名空间,包含程序中使用的各种类和方法。例如,上述命名空间包含一个名为map-invert的函数,用于反转键值对映射。除非我们明确告诉程序包含此命名空间,否则无法使用此函数。
让我们看一下命名空间可用的不同方法。
序号 | 方法和描述 |
---|---|
1 | *ns*
用于查看当前命名空间。 |
2 | ns
用于创建一个新的命名空间并将其与正在运行的程序关联。 |
3 | alias
在当前命名空间中添加指向另一个命名空间的别名。参数是两个符号:要使用的别名和目标命名空间的符号名称。 |
4 | all-ns
返回所有命名空间的列表。 |
5 | find-ns
查找并返回特定命名空间。 |
6 | ns-name
返回特定命名空间的名称。 |
7 | ns-aliases
返回与任何命名空间关联的别名。 |
8 | ns-map
返回命名空间所有映射的映射。 |
9 | un-alias
返回一个映射,该映射仅包含其键位于 keys 中的映射中的那些条目。 |
Clojure - 异常处理
任何编程语言都需要异常处理来处理运行时错误,以便维护应用程序的正常流程。异常通常会中断应用程序的正常流程,这就是为什么我们需要在应用程序中使用异常处理的原因。
异常大致分为以下几类:
已检查异常 - 除RuntimeException和Error之外,扩展Throwable类的类被称为已检查异常。例如IOException、SQLException等。已检查异常在编译时进行检查。
让我们考虑以下程序,它对名为Example.txt的文件执行操作。但是,始终可能存在Example.txt文件不存在的情况。
(ns clojure.examples.example (:gen-class)) ;; This program displays Hello World (defn Example [] (def string1 (slurp "Example.txt")) (println string1)) (Example)
如果Example.txt文件不存在,则程序将生成以下异常。
Caused by: java.io.FileNotFoundException: Example.txt (No such file or directory) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) at clojure.java.io$fn__9185.invoke(io.clj:229) at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69) at clojure.java.io$fn__9197.invoke(io.clj:258) at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)
从上面的异常中,我们可以清楚地看到程序引发了FileNotFoundException。
未检查异常 - 扩展RuntimeException类的类被称为未检查异常。例如,ArithmeticException、NullPointerException、ArrayIndexOutOfBoundsException等。未检查异常不在编译时检查,而是在运行时检查。
一个经典的例子是ArrayIndexOutOfBoundsException,当您尝试访问数组索引大于数组长度时发生这种情况。以下是一个这种错误的典型示例。
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (aget (int-array [1 2 3]) 5) (catch Exception e (println (str "caught exception: " (.toString e)))) (finally (println "This is our final block"))) (println "Let's move on")) (Example)
执行上述代码时,将引发以下异常。
caught exception: java.lang.ArrayIndexOutOfBoundsException: 5 This is our final block Let's move on
错误
错误是不可恢复的,例如OutOfMemoryError、VirtualMachineError、AssertionError等。这些是程序永远无法从中恢复的错误,并且会导致程序崩溃。现在我们需要一些机制来捕获这些异常,以便程序在存在这些异常的情况下可以继续运行。
下图显示了Clojure中异常的继承结构是如何组织的。它全部基于Java中定义的继承结构。
捕获异常
与其他编程语言一样,Clojure提供正常的“try-catch”块来捕获异常。
以下是try-catch块的一般语法。
(try (//Protected code) catch Exception e1) (//Catch block)
所有可能引发异常的代码都放在受保护的代码块中。
在catch块中,您可以编写自定义代码来处理异常,以便应用程序可以从异常中恢复。
让我们看看我们前面生成的找不到文件的异常的示例,并了解如何使用try-catch块来捕获程序引发的异常。
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch Exception e (println (str "caught exception: " (.getMessage e)))))) (Example)
上述程序产生以下输出。
caught exception: Example.txt (No such file or directory)
在上面的代码中,我们将有问题的代码包装在try块中。在catch块中,我们只是捕获异常并输出一条消息,指示发生了异常。因此,我们现在有了一种有意义的方法来捕获程序生成的异常。
多个Catch块
可以使用多个catch块来处理多种类型的异常。对于每个catch块,根据引发的异常类型,您可以编写代码来相应地处理它。
让我们修改之前的代码,包括两个catch块,一个针对我们找不到文件的特定异常,另一个是针对一般异常块。
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.getMessage e)))) (catch Exception e (println (str "caught exception: " (.getMessage e))))) (println "Let's move on")) (Example)
上述程序产生以下输出。
caught file exception: Example.txt (No such file or directory) Let's move on
从上面的输出中,我们可以清楚地看到我们的异常被“FileNotFoundException”catch块捕获,而不是一般的catch块。
Finally块
finally块位于try块或catch块之后。无论是否发生异常,finally块中的代码都会始终执行。
使用finally块允许您运行任何您想要执行的清理类型语句,无论受保护代码中发生什么情况。以下是此块的语法。
(try (//Protected code) catch Exception e1) (//Catch block) (finally //Cleanup code)
让我们修改上面的代码并添加finally代码块。以下是代码片段。
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.getMessage e)))) (catch Exception e (println (str "caught exception: " (.getMessage e)))) (finally (println "This is our final block"))) (println "Let's move on")) (Example)
上述程序产生以下输出。
caught file exception: Example.txt (No such file or directory) This is our final block Let's move on
从上面的程序中,您可以看到finally块也在catch块捕获所需异常之后实现。
由于Clojure从Java继承其异常处理机制,与Java类似,Clojure中提供了以下方法来管理异常。
public String getMessage() - 返回关于发生的异常的详细消息。此消息在Throwable构造函数中初始化。
public Throwable getCause() - 返回异常的原因,以Throwable对象表示。
public String toString() - 返回类的名称与getMessage()的结果连接。
public void printStackTrace() - 将toString()的结果以及堆栈跟踪打印到System.err(错误输出流)。
public StackTraceElement [] getStackTrace() - 返回一个包含堆栈跟踪中每个元素的数组。索引为0的元素表示调用堆栈的顶部,数组中的最后一个元素表示调用堆栈底部的函数。
public Throwable fillInStackTrace() - 使用当前堆栈跟踪填充此Throwable对象的堆栈跟踪,添加到堆栈跟踪中的任何先前信息。
以下是使用上面列出的一些方法的示例代码。
(ns clojure.examples.example (:gen-class)) (defn Example [] (try (def string1 (slurp "Example.txt")) (println string1) (catch java.io.FileNotFoundException e (println (str "caught file exception: " (.toString e)))) (catch Exception e (println (str "caught exception: " (.toString e)))) (finally (println "This is our final block"))) (println "Let's move on")) (Example)
上述程序产生以下输出。
caught file exception: java.io.FileNotFoundException: Example.txt (No such file or directory) This is our final block Let's move on
Clojure - 序列
序列是借助‘seq’命令创建的。以下是一个简单的序列创建示例。
(ns clojure.examples.example (:gen-class)) ;; This program displays Hello World (defn Example [] (println (seq [1 2 3]))) (Example)
上述程序产生以下输出。
(1 2 3)
以下是序列可用的各种方法。
序号 | 方法和描述 |
---|---|
1 | cons
返回一个新的序列,其中‘x’是第一个元素,‘seq’是其余部分。 |
2 | conj
返回一个新的序列,其中‘x’是添加到序列末尾的元素。 |
3 | concat
用于将两个序列连接在一起。 |
4 | distinct
用于确保仅将不同的元素添加到序列中。 |
5 | reverse
反转序列中的元素。 |
6 | first
返回序列的第一个元素。 |
7 | last
返回序列的最后一个元素。 |
8 | rest
返回整个序列,除了第一个元素。 |
9 | sort
返回排序后的元素序列。 |
10 | drop
根据需要删除的元素数量,从序列中删除元素。 |
11 | take-last
从序列中获取最后的元素列表。 |
12 | take
从序列中获取最前面的元素列表。 |
13 | split-at
将项目序列分成两部分。指定一个位置,在此位置进行分割。 |
Clojure - 正则表达式
正则表达式是一种用于在文本中查找子字符串的模式。正则表达式用于各种编程语言,并且在LISP类型编程语言中大量使用。
以下是一个正则表达式的示例。
//d+
上述正则表达式用于查找字符串中一个或多个数字的出现。“//”字符用于确保“d”和“+”字符用于表示正则表达式。
一般来说,正则表达式遵循以下规则集。
有两个特殊的定位字符用于表示行的开头和结尾:脱字符号 (^) 和美元符号 ($)。
正则表达式还可以包含量词。加号 (+) 表示一次或多次,应用于表达式的前面元素。星号 (*) 用于表示零次或多次出现。问号 (?) 表示零次或一次。
元字符 { 和 } 用于匹配前面字符的特定数量的实例。
在正则表达式中,句点符号 (.) 可以表示任何字符。这被称为通配符。
正则表达式可能包含字符类。一组字符可以作为包含在元字符 [ 和 ] 中的简单字符序列给出,如 [aeiou]。对于字母或数字范围,您可以使用短划线分隔符,如 [a–z] 或 [a–mA–M]。字符类的补集由方括号内的前导脱字符号表示,如 [∧a–z],表示除指定字符以外的所有字符。
正则表达式可以使用以下方法。
序号 | 方法和描述 |
---|---|
1 | re-pattern
返回java.util.regex.Pattern的一个实例。这随后用于进一步的模式匹配方法。 |
2 | refind
返回字符串对模式的下一个正则表达式匹配(如果存在),使用java.util.regex.Matcher.find()。 |
3 | replace
replace函数用于将字符串中的子字符串替换为新的字符串值。使用模式搜索子字符串。 |
4 | replace-first
replace函数用于将字符串中的子字符串替换为新的字符串值,但仅适用于子字符串的第一次出现。使用模式搜索子字符串。 |
Clojure - 断言
谓词是评估条件并提供真或假值的函数。我们在关于数字章节的示例中已经看到过谓词函数。我们已经看到诸如‘even?’之类的函数,用于测试数字是否为偶数,或者‘neg?’,用于测试数字是否大于零。所有这些函数都返回真或假值。
以下是Clojure中谓词的示例。
(ns clojure.examples.example (:gen-class)) ;; This program displays Hello World (defn Example [] (def x (even? 0)) (println x) (def x (neg? 2)) (println x) (def x (odd? 3)) (println x) (def x (pos? 3)) (println x)) (Example)
上述程序产生以下输出。
true false true true
除了普通的谓词函数外,Clojure还为谓词提供了更多函数。谓词可以使用以下方法。
序号 | 方法和描述 |
---|---|
1 | every-pred
获取一组谓词并返回一个函数‘f’,如果其所有组成谓词对所有参数返回逻辑真值,则返回真,否则返回假。 |
2 | every?
如果谓词对每个值都为真,则返回真,否则返回假。 |
3 | some
返回集合中任何谓词值的第一个逻辑真值。 |
4 | not-any?
如果集合中值的任何谓词为逻辑真,则返回假,否则返回真。 |
Clojure - 解构
解构是Clojure中的一个功能,它允许从数据结构(例如向量)中提取值并将它们绑定到符号,而无需显式遍历数据结构。
让我们来看一个解构的具体含义以及它是如何发生的示例。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def my-vector [1 2 3 4]) (let [[a b c d] my-vector] (println a b c d))) (Example)
上述程序产生以下输出。
输出
1 2 3 4
在上面的示例中,需要注意以下几点:
我们定义了一个整数向量为1、2、3和4。
然后我们使用‘let’语句将4个变量(a、b、c和d)直接赋值给my-vector变量。
如果我们对这四个变量运行‘println’语句,我们可以看到它们已经被分别赋值为向量中的值。
因此,Clojure在使用‘let’语句赋值时,解构了包含四个值的my-vector变量。然后,四个解构后的值被相应地赋值给四个参数。
如果有多余的变量没有对应的值可以赋值,那么它们将被赋值为nil。下面的例子清楚地说明了这一点。
示例
(ns clojure.examples.hello (:gen-class)) (defn Example [] (def my-vector [1 2 3 4]) (let [[a b c d e] my-vector] (println a b c d e))) (Example)
上面的程序产生以下输出。从输出中可以看到,由于最后一个变量‘e’在向量中没有对应的值,它被赋值为nil。
输出
1 2 3 4 nil
the-rest
‘the-rest’变量用于存储无法分配给任何变量的剩余值。
以下程序展示了它的使用方法。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def my-vector [1 2 3 4]) (let [[a b & the-rest] my-vector] (println a b the-rest))) (Example)
上面的程序产生以下输出。从输出中可以清楚地看到,3和4的值无法分配给任何变量,因此它们被分配给‘the-rest’变量。
输出
1 2 (3 4)
解构映射
就像向量一样,映射也可以被解构。以下是实现此操作的示例。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def my-map {"a" 1 "b" 2}) (let [{a "a" b "b"} my-map] (println a b))) (Example)
上面的程序产生以下输出。从程序中可以清楚地看到,映射的值“a”和“b”分别赋值给变量a和b。
输出
1 2
与向量类似,如果在解构发生时映射中没有对应的值,则变量将被赋值为nil。
以下是一个示例。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def my-map {"a" 1 "b" 2}) (let [{a "a" b "b" c "c"} my-map] (println a b c))) (Example)
上述程序产生以下输出。
输出
1 2 nil
Clojure - 日期和时间
由于Clojure框架是从Java类派生的,因此可以在Clojure中使用Java中提供的日期时间类。date类表示时间中的特定瞬间,精度为毫秒。
以下是日期时间类可用的方法。
java.util.Date
这用于在Clojure中创建日期对象。
语法
以下是语法。
java.util.Date.
参数 - 无。
返回值 - 分配一个Date对象并对其进行初始化,使其表示分配它的时间,精确到毫秒。
示例
以下程序展示了它的使用方法。
(ns example) (defn Example [] (def date (.toString (java.util.Date.))) (println date)) (Example)
输出
上面的程序产生以下输出。这将取决于运行程序的系统的当前日期和时间。
Tue Mar 01 06:11:17 UTC 2016
java.text.SimpleDateFormat
这用于格式化日期输出。
语法
以下是语法。
(java.text.SimpleDateFormat. format dt)
参数 - ‘format’是格式化日期时要使用的格式。‘dt’是要格式化的日期。
返回值 - 格式化的日期输出。
示例
以下程序展示了它的使用方法。
(ns example) (defn Example [] (def date (.format (java.text.SimpleDateFormat. "MM/dd/yyyy") (new java.util.Date))) (println date)) (Example)
输出
上面的程序产生以下输出。这将取决于运行程序的系统的当前日期和时间。
03/01/2016
getTime
返回自1970年1月1日00:00:00 GMT以来,此Date对象所代表的毫秒数。
语法
以下是语法。
(.getTime)
参数 - 无。
返回值 - 自1970年1月1日00:00:00 GMT以来,此日期所代表的毫秒数。
示例
以下程序展示了它的使用方法。
(ns example) (import java.util.Date) (defn Example [] (def date (.getTime (java.util.Date.))) (println date)) (Example)
输出
上面的程序产生以下输出。这将取决于运行程序的系统的当前日期和时间。
1456812778160
Clojure - 原子
原子 (Atoms) 是Clojure中一种数据类型,它提供了一种管理共享的、同步的、独立状态的方法。原子就像任何其他编程语言中的任何引用类型一样。原子的主要用途是保存Clojure的不可变数据结构。原子的值通过swap! 方法更改。
在内部,swap! 读取当前值,将函数应用于它,并尝试对其进行比较并设置。由于另一个线程可能在期间更改了该值,因此它可能需要重试,并且在自旋循环中这样做。最终效果是该值将始终是将提供的函数原子地应用于当前值的结果。
示例
原子是借助atom方法创建的。以下程序显示了一个示例。
(ns clojure.examples.example (:gen-class)) (defn example [] (def myatom (atom 1)) (println @myatom)) (example)
输出
上面的程序产生以下结果。
1
使用@符号访问原子的值。Clojure有一些可以对原子执行的操作。以下是这些操作。
序号 | 操作与描述 |
---|---|
1 | reset!
将原子的值设置为新值,而不考虑当前值。 |
2 | compare-and-set!
当且仅当原子的当前值与原子保存的旧值相同,才将原子的值原子地设置为新值。如果设置成功,则返回true,否则返回false。 |
3 | swap!
基于特定函数原子地将原子的值与新值交换。 |
Clojure - 元数据
在Clojure中,元数据 (Metadata) 用于注释集合中的数据或符号中存储的数据。这通常用于向底层编译器注释有关类型的元数据,但也可以用于开发人员。元数据不被视为对象值的一部分。同时,元数据是不可变的。
以下操作可以在Clojure中对元数据进行。
序号 | 操作与描述 |
---|---|
1 | meta-with
此函数用于为任何对象定义元数据映射。 |
2 | meta
此函数用于查看是否有任何元数据与对象关联。 |
3 | vary-meta
返回与原始对象类型和值相同的对象,但具有组合的元数据。 |
Clojure - StructMaps
结构映射 (StructMaps) 用于在Clojure中创建结构。例如,如果您想创建一个包含员工姓名和员工 ID 的结构,可以使用StructMaps。
以下操作可以在Clojure中对StructMaps进行。
序号 | 操作与描述 |
---|---|
1 | defstruct
此函数用于定义所需的结构。 |
2 | struct
此函数用于定义由defstruct操作创建的类型的结构对象。 |
3 | struct-map
此函数用于通过显式定义哪些值分配给结构中的哪些键来专门为键值赋值。 |
4 | 访问单个字段
可以通过访问键以及结构对象来访问结构的单个字段。 |
5 | 不可变性
默认情况下,结构也是不可变的,因此如果我们尝试更改特定键的值,它将不会更改。 |
6 | 向结构添加新键
由于结构是不可变的,因此向结构添加另一个键的唯一方法是创建新的结构。以下程序展示了如何实现这一点。 |
Clojure - 代理
正如多次指出的那样,Clojure是一种编程语言,其中许多数据类型是不可变的,这意味着更改变量值的唯一方法是创建一个新变量并将新值分配给它。但是,Clojure确实提供了一些可以创建可变状态的元素。我们已经看到这可以通过原子数据类型来实现。实现此目的的另一种方法是通过代理 (Agents)。
代理 (Agents) 提供单个位置的独立异步更改。代理在其生命周期中绑定到单个存储位置,并且只允许作为操作的结果对该位置(更改为新状态)进行变异。操作是函数(可选地,带有其他参数),它们被异步应用于代理的状态,其返回值成为代理的新状态。
以下操作可以在Clojure中对代理进行。
序号 | 操作与描述 |
---|---|
1 | agent
使用agent命令创建代理。 |
2 | send
此函数用于向代理发送值。 |
3 | shutdown-agents
此函数用于关闭任何正在运行的代理。 |
4 | send-off
在某些情况下,代理被分配一个具有阻塞性质的函数。 |
5 | await-for
由于更新代理的值时存在延迟,Clojure提供了一个‘await-for’函数,用于指定等待代理更新的毫秒数。 |
6 | await
无限期地阻塞当前线程!直到从这个线程或代理到代理的所有已分派操作发生为止。将阻塞失败的代理。 |
7 | agent-error
如果代理失败,则返回在代理的异步操作期间抛出的异常。如果代理不失败,则返回nil。 |
Clojure - 观察者
观察者 (Watchers) 是添加到原子和引用变量等变量类型中的函数,当变量类型的值发生更改时,这些函数将被调用。例如,如果调用程序更改了原子变量的值,并且如果观察者函数附加到原子变量,则一旦原子值更改,该函数将被调用。
Clojure中为观察者提供了以下函数。
add-watch
向代理/原子/var/ref 引用添加一个观察函数。观察‘fn’必须是一个具有4个参数的‘fn’:一个键,引用,其旧状态,其新状态。每当引用的状态可能已被更改时,任何已注册的观察都会调用其函数。
语法
以下是语法。
(add-watch variable :watcher (fn [key variable-type old-state new-state]))
参数 - ‘variable’是原子或引用变量的名称。‘variable-type’是变量的类型,原子或引用变量。‘old-state & new-state’是将自动保存变量的旧值和新值的参数。‘key’对于每个引用必须是唯一的,并且可以用来使用remove-watch删除观察。
返回值 - 无。
示例
以下程序展示了它的使用方法。
(ns clojure.examples.example (:gen-class)) (defn Example [] (def x (atom 0)) (add-watch x :watcher (fn [key atom old-state new-state] (println "The value of the atom has been changed") (println "old-state" old-state) (println "new-state" new-state))) (reset! x 2)) (Example)
输出
上述程序产生以下输出。
The value of the atom has been changed old-state 0 new-state 2
remove-watch
删除已附加到引用变量的观察。
语法
以下是语法。
(remove-watch variable watchname)
参数 - ‘variable’是原子或引用变量的名称。‘watchname’是在定义观察函数时给观察赋予的名称。
返回值 - 无。
示例
以下程序展示了它的使用方法。
(ns clojure.examples.example (:gen-class)) (defn Example [] (def x (atom 0)) (add-watch x :watcher (fn [key atom old-state new-state] (println "The value of the atom has been changed") (println "old-state" old-state) (println "new-state" new-state))) (reset! x 2) (remove-watch x :watcher) (reset! x 4)) (Example)
输出
上述程序产生以下输出。
The value of the atom has been changed old-state 0 new-state 2
从上面的程序可以清楚地看到,第二个reset命令不会触发观察者,因为它已从观察者的列表中删除。
Clojure - 宏
在任何语言中,宏 (Macros) 都用于生成内联代码。Clojure也不例外,它为开发人员提供了简单的宏工具。宏用于编写代码生成例程,这为开发人员提供了一种强大的方法来根据开发人员的需求定制语言。
以下是宏可用的方法。
defmacro
此函数用于定义宏。宏将具有宏名、参数列表和宏体。
语法
以下是语法。
(defmacro name [params*] body)
参数 - ‘name’是宏的名称。‘params’是分配给宏的参数。‘body’是宏的主体。
返回值 - 无。
示例
以下程序展示了它的使用方法。
(ns clojure.examples.example (:gen-class)) (defn Example [] (defmacro Simple [] (println "Hello")) (macroexpand '(Simple))) (Example)
输出
上述程序产生以下输出。
Hello
从上面的程序可以看到,宏‘Simple’被内联扩展为‘println’“Hello”。宏类似于函数,唯一的区别是对于宏,会对表单的参数进行求值。
macro-expand
这用于扩展宏并将代码内联到程序中。
语法
以下是语法。
(macroexpand macroname)
参数 - ‘macroname’是要扩展的宏的名称。
返回值 - 已扩展的宏。
示例
以下程序展示了它的使用方法。
(ns clojure.examples.example (:gen-class)) (defn Example [] (defmacro Simple [] (println "Hello")) (macroexpand '(Simple))) (Example)
输出
上述程序产生以下输出。
Hello
带参数的宏
宏也可以用于接收参数。宏可以接收任意数量的参数。下面的示例展示了如何使用参数。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (defmacro Simple [arg] (list 2 arg)) (println (macroexpand '(Simple 2)))) (Example)
上面的示例在 Simple 宏中放置了一个参数,然后使用该参数将参数值添加到列表中。
输出
上述程序产生以下输出。
(2 2)
Clojure - 引用值
引用值是 Clojure 处理对可变变量需求的另一种方式。Clojure 提供了可变数据类型,例如原子 (atoms)、代理 (agents) 和引用类型。
以下是引用值可用的操作。
序号 | 操作与描述 |
---|---|
1 | ref
这用于创建引用值。创建引用值时,可以选择提供验证器函数,该函数将验证创建的值。 |
2 | ref-set
此函数用于将引用的值设置为新值,无论旧值是什么。 |
3 | alter
此函数用于以安全的方式更改引用类型的值。这在一个线程中运行,另一个进程无法访问该线程。 |
4 | dosync
在一个事务中运行表达式(在一个隐式的 do 中),该事务包含表达式和任何嵌套调用。 |
5 | commute
commute 也用于更改引用类型的值,就像 alter 和 ref-set 一样。 |
Clojure - 数据库
为了使用数据库功能,请确保首先从以下 URL 下载jdbc 文件:https://codeload.github.com/clojure/java.jdbc/zip/master
您将找到一个 zip 文件,其中包含 Clojure 连接到数据库所需的驱动程序。解压缩 zip 文件后,请确保将解压缩后的位置添加到您的类路径中。
数据库连接的主要文件是 clojure/java 位置中的jdbc.clj文件。
Clojure jdbc 连接器支持各种数据库,其中一些如下所示。
- H2 数据库
- Oracle
- Microsoft SQL Server
- MySQL
- PostgreSQL
在我们的示例中,我们将使用 MySQL 数据库作为示例。
在 Clojure 中,关于数据库,以下操作是可能的。
数据库连接
连接到 MySQL 数据库之前,请确保以下事项:
您已创建数据库 TESTDB。
您已在 TESTDB 中创建表 EMPLOYEE。
此表包含字段 FIRST_NAME、LAST_NAME、AGE、SEX 和 INCOME。
已设置用户 ID“testuser”和密码“test123”以访问 TESTDB。
确保您已下载“mysql jar 文件”并将该文件添加到您的类路径中。
您已学习 MySQL 教程以了解MySQL 基础知识。
语法
以下是 Clojure 中创建连接的语法。
(def connection_name { :subprotocol “protocol_name” :subname “Location of mysql DB” :user “username” :password “password” })
参数:‘connection_name’ 是要赋予连接的名称。‘subprotocol’ 是用于连接的协议。默认情况下,我们将使用 mysql 协议。‘subname’ 是连接到 mysql 数据库以及数据库名称的 url。‘user’ 是用于连接到数据库的用户名。‘password’ 是用于连接到数据库的密码。
返回值:这将提供一个连接字符串,可在后续的 mysql 操作中使用。
以下示例显示了如何连接到 information_schema 中的表并检索表中的所有数据。
示例
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/information_schema" :user "root" :password "shakinstev"}) (println (sql/query mysql-db ["select table_name from tables"] :row-fn :table_name)))
查询数据
查询任何数据库中的数据意味着从数据库中获取一些有用的信息。建立数据库连接后,您就可以对该数据库进行查询了。以下是使用 Clojure 查询数据的语法。
语法
clojure.java.jdbc/query dbconn ["query"] :row-fn :sequence
参数:‘dbconn’ 是用于连接到数据库的连接的名称。‘query’ 是用于从数据库中获取数据的查询字符串。‘:sequence’ 默认情况下是从数据库中获取的所有数据行,并作为序列返回。然后可以对序列执行必要的操作以查看已获取的数据。
返回值:这将返回一个序列,其中包含查询操作的数据行。
以下示例显示了如何连接到 employee 表并获取表中行的 first_name 列。
示例
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/testdb" :user "root" :password "shakinstev"}) (println (sql/query mysql-db ["select first_name from employee"] :row-fn :first_name)))
从上面的代码中,我们可以看到
“select first_name from employee” 的查询作为查询字符串传递。
“:first_name” 是作为提取操作的结果返回的序列。
如果我们假设我们的数据库中只有一行包含 first_name 值为 John,则上述程序的输出如下。
(John)
插入数据
当您想要将记录创建到数据库表中时,这是必需的。以下是使用 Clojure 插入数据的语法。这是通过使用‘insert!’函数完成的。
语法
clojure.java.jdbc/insert! :table_name {:column_namen columnvalue}
参数:‘:table_name’ 是需要进行插入操作的表的名称。‘{:column_namen columnvalue }’ 是所有列名和值的映射,需要将其作为一行添加到表中。
返回值:如果成功插入,则返回 nil。
以下示例显示了如何将记录插入 testdb 数据库中的 employee 表中。
示例
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/testdb" :user "root" :password "shakinstev"}) (sql/insert! mysql-db :employee {:first_name "John" :last_name "Mark" :sex "M" :age 30 :income 30}))
如果您现在检查您的 MySQL 数据库和 employee 表,您将看到上述行将成功插入到表中。
删除数据
可以使用‘delete!’函数从表中删除行。以下是执行此操作的语法。
语法
clojure.java.jdbc/delete! :table_name [condition]
参数:‘:table_name’ 是需要进行插入操作的表的名称。‘condition’ 是用于确定需要从表中删除哪一行的条件。
返回值:这将返回已删除的行数。
以下示例显示了如何从 testdb 数据库中的 employee 表中删除记录。该示例根据年龄等于 30 的条件从表中删除一行。
示例
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/testdb" :user "root" :password "shakinstev"}) (println (sql/delete! mysql-db :employee ["age = ? " 30])))
如果您有一条年龄等于 30 的记录,则该行将被删除。
更新数据
可以使用‘update!’函数更新表中的行。以下是执行此操作的语法。
语法
clojure.java.jdbc/update! :table_name {setcondition} [condition]
参数:‘:table_name’ 是需要进行插入操作的表的名称。‘setcondition’ 是需要更新的列,以映射的形式表示。‘condition’ 是用于确定需要从表中删除哪一行的条件。
返回值:这将返回已更新的行数。
以下示例显示了如何从 testdb 数据库中的 employee 表中删除记录。该示例更新表中的一行,条件是年龄等于 30,并将收入值更新为 40。
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/testdb" :user "root" :password "shakinstev"}) (println (sql/update! mysql-db :employee {:income 40} ["age = ? " 30])))
如果您有一条年龄等于 30 的记录,则该行将被更新,其中收入值将设置为 40。
事务
事务是确保数据一致性的机制。事务具有以下四个属性:
原子性:事务要么完成,要么什么也不发生。
一致性:事务必须从一致状态开始,并使系统处于一致状态。
隔离性:事务的中间结果在当前事务之外不可见。
持久性:一旦事务提交,其效果就是持久的,即使在系统故障之后也是如此。
示例
以下示例显示了如何在 Clojure 中实现事务。需要在事务中执行的任何操作都需要嵌入到‘with-dbtransaction’子句中。
(ns test.core (:require [clojure.java.jdbc :as sql])) (defn -main [] (def mysql-db { :subprotocol "mysql" :subname "//127.0.0.1:3306/testdb" :user "root" :password "shakinstev"}) (sql/with-db-transaction [t-con mysql-db] (sql/update! t-con :employee {:income 40} ["age = ? " 30])))
Clojure - Java 接口
众所周知,Clojure 代码最终运行在 Java 虚拟环境上。因此,Clojure 能够利用 Java 的所有功能也就不足为奇了。在本章中,让我们讨论 Clojure 和 Java 之间的关联。
调用 Java 方法
可以使用点表示法调用 Java 方法。一个例子是字符串。由于 Clojure 中的所有字符串都是 Java 字符串,因此您可以在字符串上调用普通的 Java 方法。
以下程序显示了如何执行此操作。
示例
(ns Project (:gen-class)) (defn Example [] (println (.toUpperCase "Hello World"))) (Example)
上述程序产生以下输出。您可以从代码中看到,如果您只对任何字符串方法调用点表示法,它也将在 Clojure 中工作。
输出
HELLO WORLD
使用参数调用 Java 方法
您还可以使用参数调用 Java 方法。以下程序显示了如何执行此操作。
示例
(ns Project (:gen-class)) (defn Example [] (println (.indexOf "Hello World","e"))) (Example)
上述程序产生以下输出。您可以从上面的代码中看到,我们正在将参数“e”传递给 indexOf 方法。上述程序产生以下输出。
输出
1
创建 Java 对象
可以使用与 Java 中类似的‘new’关键字在 Clojure 中创建对象。
以下程序显示了如何执行此操作。
示例
(ns Project (:gen-class)) (defn Example [] (def str1 (new String "Hello")) (println str1)) (Example)
上述程序产生以下输出。您可以从上面的代码中看到,我们可以使用‘new’关键字从 Java 中现有的 String 类创建一个新对象。我们可以像在 Java 中一样在创建对象时传递值。上述程序产生以下输出。
输出
Hello
以下另一个示例显示了我们如何创建 Integer 类的对象并在普通的 Clojure 命令中使用它们。
示例
(ns Project (:gen-class)) (defn Example [] (def my-int(new Integer 1)) (println (+ 2 my-int))) (Example)
上述程序产生以下输出。
输出
3
import 命令
我们还可以使用 import 命令将 Java 库包含到命名空间中,以便可以轻松访问类和方法。
以下示例显示了如何使用 import 命令。在示例中,我们使用 import 命令来导入java.util.stack库中的类。然后,我们可以按原样使用 stack 类的 push 和 pop 方法。
示例
(ns Project (:gen-class)) (import java.util.Stack) (defn Example [] (let [stack (Stack.)] (.push stack "First Element") (.push stack "Second Element") (println (first stack)))) (Example)
上述程序产生以下输出。
输出
First Element
使用 Java 命令运行代码
可以使用 Java 命令运行 Clojure 代码。以下是执行此操作的语法。
java -jar clojure-1.2.0.jar -i main.clj
您必须提及 Clojure jar 文件,以便所有基于 Clojure 的类都将在 JVM 中加载。‘main.clj’ 文件是要执行的 Clojure 代码文件。
Java 内置函数
Clojure 可以使用许多 Java 的内置函数。其中一些是:
Math PI 函数:Clojure 可以使用 Math 方法获取 PI 的值。以下是一个示例代码。
示例
(ns Project (:gen-class)) (defn Example [] (println (. Math PI))) (Example)
上述代码产生以下输出。
输出
3.141592653589793
系统属性:Clojure 也可以查询系统属性。以下是一个示例代码。
示例
(ns Project (:gen-class)) (defn Example [] (println (.. System getProperties (get "java.version")))) (Example)
根据系统上 Java 的版本,将显示相应的值。以下是一个示例输出。
输出
1.8.0_45
Clojure - 并发编程
在 Clojure 编程中,大多数数据类型是不可变的,因此在并发编程中,当代码在多个处理器上运行时,使用这些数据类型的代码非常安全。但是很多时候,需要共享数据,当涉及到跨多个处理器的共享数据时,在使用多个处理器时,就必须确保数据的状态在完整性方面得到维护。这被称为并发编程,Clojure 为此类编程提供了支持。
通过 `dosync`、`ref`、`set`、`alter` 等公开的软件事务内存系统 (STM) 支持以同步和协调的方式在多个线程之间共享变化状态。代理系统支持以异步和独立的方式在多个线程之间共享变化状态。原子系统支持以同步和独立的方式在多个线程之间共享变化状态。而动态变量系统(通过 `def`、`binding` 等公开)支持在线程内隔离变化状态。
其他编程语言也遵循并发编程的类似模型。
它们可以直接引用可以更改的数据。
如果需要共享访问,则锁定对象,更改值,然后继续访问该值的下一个过程。
在 Clojure 中没有锁,而是对不可变持久数据结构的间接引用。
Clojure 中有三种类型的引用。
Vars − 更改在线程中被隔离。
Refs − 更改在线程之间同步和协调。
Agents − 涉及线程之间的异步独立更改。
在 Clojure 中,关于并发编程,可以进行以下操作。
事务
Clojure 中的并发基于事务。引用只能在事务中更改。事务中应用以下规则。
- 所有更改都是原子且隔离的。
- 对引用的每次更改都在事务中发生。
- 没有任何事务可以看到另一个事务所做的更改。
- 所有事务都放置在 `dosync` 块内。
我们已经看到了 `dosync` 块的作用,让我们再来看一下。
dosync
在一个包含表达式和任何嵌套调用的事务中运行表达式(在隐式 `do` 中)。如果当前线程上没有运行事务,则启动一个事务。任何未捕获的异常都将中止事务并从 `dosync` 中流出。
以下是语法。
语法
(dosync expression)
参数 − ‘表达式’ 是将出现在 `dosync` 块中的一组表达式。
返回值 - 无。
让我们来看一个尝试更改引用变量值的示例。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def names (ref [])) (alter names conj "Mark")) (Example)
输出
运行上述程序时会给出以下错误。
Caused by: java.lang.IllegalStateException: No transaction running at clojure.lang.LockingTransaction.getEx(LockingTransaction.java:208) at clojure.lang.Ref.alter(Ref.java:173) at clojure.core$alter.doInvoke(core.clj:1866) at clojure.lang.RestFn.invoke(RestFn.java:443) at clojure.examples.example$Example.invoke(main.clj:5) at clojure.examples.example$eval8.invoke(main.clj:7) at clojure.lang.Compiler.eval(Compiler.java:5424) ... 12 more
从错误中可以清楚地看到,如果不先启动事务,就不能更改引用类型的值。
为了使上述代码能够工作,我们必须将 `alter` 命令放在 `dosync` 块中,如下面的程序所示。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def names (ref [])) (defn change [newname] (dosync (alter names conj newname))) (change "John") (change "Mark") (println @names)) (Example)
上述程序产生以下输出。
输出
[John Mark]
让我们来看另一个 `dosync` 的示例。
示例
(ns clojure.examples.example (:gen-class)) (defn Example [] (def var1 (ref 10)) (def var2 (ref 20)) (println @var1 @var2) (defn change-value [var1 var2 newvalue] (dosync (alter var1 - newvalue) (alter var2 + newvalue))) (change-value var1 var2 20) (println @var1 @var2)) (Example)
在上面的示例中,我们在 `dosync` 块中更改了两个值。如果事务成功,则两个值都将更改;否则,整个事务将失败。
上述程序产生以下输出。
输出
10 20 -10 40
Clojure - 应用
Clojure 有些贡献的库可以用来创建桌面和基于 Web 的应用程序。让我们讨论一下每一个。
序号 | 应用程序及说明 |
---|---|
1 | 桌面 – See-saw
See-saw 是一个可用于创建桌面应用程序的库。 |
2 | 桌面 – 更改文本的值
窗口中内容的值可以使用`config!`选项更改。在下面的示例中,`config!` 选项用于将窗口内容更改为新的值“Good Bye”。 |
3 | 桌面 – 显示模式对话框
可以使用 see-saw 类的 alert 方法显示模式对话框。该方法接受需要在模式对话框中显示的文本值。 |
4 | 桌面 – 显示按钮
可以使用 button 类显示按钮。 |
5 | 桌面 – 显示标签
可以使用 label 类显示标签。 |
6 | 桌面 – 显示文本字段
可以使用 text 类显示文本字段。 |
Web 应用程序 - 简介
要在 Clojure 中创建 Web 应用程序,需要使用 Ring 应用程序库,该库位于以下链接 https://github.com/ring-clojure/ring
您需要确保从网站下载必要的 jar 文件,并确保将其添加为 Clojure 应用程序的依赖项。
Ring 框架提供以下功能:
将事情设置好,以便 HTTP 请求作为常规 Clojure HashMap 进入您的 Web 应用程序,同样,它可以让您将响应作为 HashMap 返回。
提供一个规范,准确描述这些请求和响应映射应该是什么样子。
自带一个 Web 服务器 (Jetty) 并将其连接到您的 Web 应用程序。
Ring 框架可以自动启动 Web 服务器并确保 Clojure 应用程序在此服务器上运行。然后还可以使用 Compojure 框架。这允许您创建路由,这正是大多数现代 Web 应用程序的开发方式。
创建您的第一个 Clojure 应用程序 − 下面的示例显示了如何创建您的第一个 Clojure Web 应用程序。
(ns my-webapp.handler (:require [compojure.core :refer :all] [compojure.route :as route] [ring.middleware.defaults :refer [wrap-defaults site-defaults]])) (defroutes app-routes (GET "/" [] "Hello World") (route/not-found "Not Found")) (def app (wrap-defaults app-routes site-defaults))
让我们看一下程序的以下方面:
`defroutes` 用于创建路由,以便可以将对 Web 应用程序的不同路由的请求定向到 Clojure 应用程序中的不同函数。
在上面的示例中,“/”被称为默认路由,因此当您浏览到 Web 应用程序的根目录时,字符串“Hello World”将发送到 Web 浏览器。
如果用户访问 Clojure 应用程序无法处理的任何 URL,则它将显示字符串“Not Found”。
运行 Clojure 应用程序时,您的应用程序默认情况下将加载为 localhost:3000,因此如果您浏览到此位置,您将收到以下输出。
Web 应用程序 – 向 Web 应用程序添加更多路由
您还可以向 Web 应用程序添加更多路由。以下示例显示了如何实现此目的。
(ns my-webapp.handler (:require [compojure.core :refer :all] [compojure.route :as route] [ring.middleware.defaults :refer [wrap-defaults site-defaults]])) (defroutes app-routes (GET "/" [] "Hello World") (GET "/Tutorial" [] "This is a tutorial on Clojure") (route/not-found "Not Found")) (def app (wrap-defaults app-routes site-defaults))
您可以看到,在应用程序中添加路由就像添加另一个带有 URL 路由的 GET 函数一样简单。(GET "/Tutorial" [] "This is a tutorial on Clojure")
如果您浏览到https://127.0.0.1:3000/Tutorial,您将收到以下输出。
Clojure - 自动化测试
在本章中,让我们讨论 Clojure 提供的自动化测试选项。
客户端应用程序测试
为了使用 Clojure 框架进行测试,您必须使用位于 https://github.com/slagyr/speclj#manual-installation 的依赖项
此 URL 提供了speclj框架,该框架用作 Clojure 的测试数据驱动或行为驱动测试框架。使用任何“speclj”库时,您必须确保使用 Clojure 1.7.0 框架。默认情况下,测试文件将与 Clojure 代码文件不同,需要放在“spec”目录中。
以下是测试文件的示例代码。
(ns change.core-spec (:require [speclj.core :refer :all])) (describe "Truth" (it "is true" (should true)) (it "is not false" (should-not false))) (run-specs)
关于上述代码,需要注意以下几点:
我们首先必须确保使用 `require` 语句包含 `speclj` 框架中的所有核心库。
接下来是 `describe` 函数。这用于为正在创建的测试用例提供描述。
下一个函数是 `it` 函数,它是实际的测试用例。在第一个测试用例中,“is true”字符串是赋予测试用例的名称。
`should` 和 `should-not` 被称为断言。所有断言都以 `should` 开头。`should` 和 `should-not` 只是许多断言中的两个。它们都采用它们将检查真假性的表达式。
如果运行测试用例,您将获得以下输出。输出显示测试用例运行所花费的时间(以毫秒为单位)。
←[32m.←[0m←[32m.←[0m Finished in 0.00014 seconds
基于 Web 的应用程序测试
Selenium是用于测试现代基于 Web 的应用程序的关键框架之一。Clojure 库也可用,可用于测试基于 Web 的应用程序。
让我们看看如何使用 Selenium 库测试基于 Clojure 的 Web 应用程序。
步骤 1 − 第一步是确保我们使用 Ring 和 Compojure 框架来创建一个需要测试的基于 Web 的应用程序。让我们使用前面章节中的一个示例。以下代码是一个简单的 Web 应用程序,它在浏览器中显示“Hello World”。
(ns my-webapp.handler (:require [compojure.core :refer :all] [compojure.route :as route] [ring.middleware.defaults :refer [wrap-defaults site-defaults]])) (defroutes app-routes (GET "/" [] "Hello World") (route/not-found "Not Found")) (def app (wrap-defaults app-routes site-defaults))
步骤 2 − 接下来,确保下载 selenium jar 文件 https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-server/2.47.0 并将其包含在您的类路径中。
步骤 3 − 还确保下载 `clj` web driver,它将用于从以下位置运行 Web 测试。
https://clojars.org/clj-webdriver/versions/0.7.1
步骤 4 − 在您的项目目录中,创建一个名为 features 的另一个目录,并创建一个名为 `config.clj` 的文件。
步骤 5 − 接下来,将以下代码添加到前面步骤中创建的 `config.clj` 文件中。
ns clj-webdriver-tutorial.features.config) (def test-port 3000) (def test-host "localhost") (def test-base-url (str "http://" test-host ":" test-port "/"))
上述代码基本上告诉 Web 测试框架测试在 URL https://127.0.0.1:3000 加载的应用程序
步骤 6 − 最后,让我们编写代码来执行我们的测试。
(ns clj-webdriver-tutorial.features.homepage (:require [clojure.test :refer :all] [ring.adapter.jetty :refer [run-jetty]] [clj-webdriver.taxi :refer :all] [clj-webdriver-tutorial.features.config :refer :all] [clj-webdriver-tutorial.handler :refer [app-routes]])) (ns clj-webdriver-tutorial.features.homepage (:require [clojure.test :refer :all] [ring.adapter.jetty :refer [run-jetty]] [clj-webdriver.taxi :refer :all] [clj-webdriver-tutorial.features.config :refer :all] [clj-webdriver-tutorial.handler :refer [app-routes]])) (defn start-server [] (loop [server (run-jetty app-routes {:port test-port, :join? false})] (if (.isStarted server) server (recur server)))) (defn stop-server [server] (.stop server)) (defn start-browser [] (set-driver! {:browser :firefox})) (defn stop-browser [] (quit)) (deftest homepage-greeting (let [server (start-server)] (start-browser) (to test-base-url) (is (= (text "body") "Hello World")) (stop-browser) (stop-server server)))
上面的代码将执行以下操作:
- 启动应用程序的服务器。
- 在浏览器中打开根路径。
- 检查页面上是否存在“Hello World”消息。
- 关闭浏览器。
- 关闭服务器。
Clojure - 库
使 Clojure 库如此强大的一个方面是 Clojure 框架可用的库数量。我们已经在前面的示例中看到了许多用于 Web 测试、Web 开发、开发基于 Swing 的应用程序、用于连接到 MySQL 数据库的 jdbc 库的库。以下只是一些其他库的几个示例。
data.xml
此库允许 Clojure 使用 XML 数据。要使用的库版本是 org.clojure/data.xml "0.0.8"。data.xml 支持解析和输出 XML。解析函数将从 Reader 或 InputStream 读取 XML。
示例
以下是将字符串数据处理为 XML 的示例。
(ns clojure.examples.example (use 'clojure.data.xml) (:gen-class)) (defn Example [] (let [input-xml (java.io.StringReader. "<?xml version = \"1.0\" encoding = \"UTF-8\"?><example><clo><Tutorial>The Tutorial value</Tutorial></clo></example>")] (parse input-xml))) #clojure.data.xml.Element{ :tag :example, :attrs {}, :content (#clojure.data.xml.Element { :tag :clo, :attrs {}, :content (#clojure.data.xml.Element { :tag :Tutorial, :attrs {},:content ("The Tutorial value")})})} (Example)
data.json
此库允许 Clojure 使用 JSON 数据。要使用的库版本是 org.clojure/data.json "0.2.6"。
示例
以下是如何使用此库的示例。
(ns clojure.examples.example (:require [clojure.data.json :as json]) (:gen-class)) (defn Example [] (println (json/write-str {:a 1 :b 2}))) (Example)
输出
上述程序产生以下输出。
{\"a\":1,\"b\":2}
data.csv
此库允许 Clojure 使用`csv`数据。要使用的库版本是 org.clojure/data.csv "0.1.3"。
示例
以下是如何使用此库的示例。
(ns clojure.examples.example (require '[clojure.data.csv :as csv] '[clojure.java.io :as io]) (:gen-class)) (defn Example [] (with-open [in-file (io/reader "in-file.csv")] (doall (csv/read-csv in-file))) (with-open [out-file (io/writer "out-file.csv")] (csv/write-csv out-file [[":A" "a"] [":B" "b"]]))) (Example)
在上面的代码中,`csv` 函数将首先读取名为 `in-file.csv` 的文件并将所有数据放入变量 `in-file` 中。接下来,我们使用 `write-csv` 函数将所有数据写入名为 `out-file.csv` 的文件。