目录

Clojure - 快速指南

Clojure - Overview

Clojure是一种高级动态函数式编程语言。 Clojure是基于LISP编程语言设计的,并且具有使其在Java和.Net运行时环境中运行的编译器。

在我们讨论Clojure之前,让我们快速描述一下LISP编程语言。 LISP具有很小的语言核心,几乎没有语法,并且具有强大的宏功能。 通过这些功能,您可以弯曲LISP以满足您的设计,而不是相反。 LISP已经存在了很长一段时间,可以追溯到1958年。

公共LISP读入表达式,对其进行求值,然后打印出结果。 例如,如果要计算4 + 6的简单数学表达式的值,则键入。

USER(1) (+ 4 6)

Clojure作为一种编程语言具有以下高级关键目标。

  • 它基于LISP编程语言,使其代码语句比传统编程语言更小。

  • 它是一种函数式编程语言。

  • 它侧重于不变性,这基本上是您不应该对就地创建的对象进行任何更改的概念。

  • 它可以管理程序员的应用程序状态。

  • 它支持并发。

  • 它包含现有的编程语言。 例如,Clojure可以利用整个Java生态系统来管理通过JVM运行的代码。

Clojure的官方网站是https://clojure.org/

Clojure概述

Clojure - Environment

使用Clojure作为编程语言有多种方法。 我们将介绍两种使用Clojure编程的方法。

  • Leiningen - Leiningen是创建,构建和自动化Clojure项目的重要工具。

  • Eclipse Plugin - 有一个名为CounterClockwise的插件,可用于Eclipse在Eclipse IDE中执行Clojure开发。

莱宁根安装

在继续安装之前,请确保满足以下系统要求。

系统需求 (System Requirements)

JDK JDK 1.7或以上
记忆 2 GB RAM(推荐)

Step 1 - 下载二进制安装。 转到http://leiningen-wininstaller以获取Windows Installer。 单击该选项以开始下载Groovy安装程序。

Step 2 - 启动安装程序,然后单击“下一步”按钮。

启动安装程序

Step 3 - 指定安装位置,然后单击“下一步”按钮。

位置安装

Step 4 - 安装程序将检测现有Java安装的位置。 单击“下一步”按钮继续。

Java安装

Step 5 - 单击“安装”按钮开始安装。

开始安装

安装完成后,它将为您提供打开Clojure REPL的选项,这是一个可用于创建和测试Clojure程序的环境。

Clojure计划

Eclipse安装

在继续安装之前,请确保满足以下系统要求。

系统需求 (System Requirements)

JDK JDK 1.7或以上
日食 Eclipse 4.5(火星)

Step 1 - 打开Eclipse并单击“菜单”项。 单击帮助→Eclipse Marketplace。

Eclipse Marketplace

Step 2 - 在出现的对话框中输入关键字Clojure,然后点击“开始”按钮。 将出现逆时针选项,单击“安装”按钮开始安装此插件。

Clojure对话框

Step 3 - 在下一个对话框中,单击“确认”按钮开始安装。

Clojure确认按钮

Step 4 - 在下一个对话框中,将要求您接受许可协议。 接受许可协议,然后单击“完成”按钮继续安装。

接受许可协议

安装将开始,一旦完成,它将提示您重新启动Eclipse。

重新启动Eclipse后,您将在Eclipse中看到用于创建新Clojure项目的选项。

Clojure项目

Clojure - Basic Syntax

为了理解Clojure的基本语法,让我们首先看一个简单的Hello World程序。

Hello World作为一个完整的程序

在完整的Clojure程序中写下“Hello world”。 以下是一个例子。

例子 (Example)

(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'语句。

上述程序产生以下输出。

输出 (Output)

Hello World

声明的一般形式

任何语句的一般形式都需要在大括号中进行评估,如以下示例所示。

(+ 1 2)

在上面的示例中,整个表达式用大括号括起来。 上述语句的输出为3. +运算符的作用类似于Clojure中的一个函数,用于添加数字。 值1和2被称为parameters to the function

让我们考虑另一个例子。 在这个例子中,'str'是用于连接两个字符串的运算符。 字符串“Hello”和“World”用作参数。

(str "Hello" "World")

例子 (Example)

如果我们结合上述两个语句并编写程序,它将如下所示。

(ns clojure.examples.hello
   (:gen-class))
(defn Example []
   (println (str "Hello World"))
   (println (+ 1 2)))
(Example)

输出 (Output)

上述程序产生以下输出。

Hello World
3

Namespaces

命名空间用于定义Clojure中定义的模块之间的逻辑边界。

当前命名空间

这定义了当前Clojure代码所在的当前名称空间。

语法 (Syntax)

*ns*

例子 (Example)

在REPL命令窗口中,运行以下命令。

*ns*

输出 (Output)

当我们运行上面的命令时,输出将推迟,具体取决于当前命名空间。 以下是输出的示例。 Clojure代码的命名空间是 -

clojure.examples.hello
(ns clojure.examples.hello
   (:gen-class))
(defn Example []
   (println (str "Hello World"))
   (println (+ 1 2)))
(Example)

在Clojure中要求声明

Clojure代码打包在库中。 每个Clojure库都属于一个名称空间,类似于Java包。 您可以使用“Require”语句加载Clojure库。

语法 (Syntax)

(require quoted-namespace-symbol)

例子 (Example)

以下是此语句的使用示例。

(ns clojure.examples.hello
   (:gen-class))
(require ‘clojure.java.io’)
(defn Example []
   (.exists (file "Example.txt")))
(Example)

在上面的代码中,我们使用'require'关键字导入命名空间clojure.java.io,它具有输入/输出功能所需的所有功能。 由于我们没有所需的库,我们可以在上面的代码中使用'file'函数。

Clojure中的评论

注释用于记录您的代码。 单行注释使用;; 在任何位置。 以下是一个例子。

例子 (Example)

(ns clojure.examples.hello
   (:gen-class))
;; This program displays Hello World
(defn Example []
   (println "Hello World"))
(Example)

Delimiters

在Clojure中,可以使用弯曲或方括号括号来分割或分隔语句。

例子 (Example)

以下是两个例子。

(ns clojure.examples.hello
   (:gen-class))
;; This program displays Hello World
(defn Example []
   (println (+ 1 2 3)))
(Example)

输出 (Output)

上述程序产生以下输出。

6

例子 (Example)

以下是另一个例子。

(ns clojure.examples.hello
   (:gen-class))
;; This program displays Hello World
(defn Example []
   (println [+ 1 2 3]))
(Example)

输出 (Output)

上述程序产生以下输出。

[#object[clojure.core$_PLUS_ 0x10f163b "clojure.core$_PLUS_@10f163b"] 1 2 3]

Whitespaces

可以在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}。

结果更容易阅读,特别是如果您正在查看大量数据。

Symbols

在Clojure中,符号等同于其他编程语言中的标识符。 但与其他编程语言不同,编译器将符号视为实际的字符串值。 由于符号是值,因此符号可以存储在集合中,作为参数传递给函数等,就像任何其他对象一样。

符号只能包含字母数字字符和'* +! /。 : - _?' 但不能以数字或冒号开头。

以下是符号的有效示例。

tutorial-point!
TUTORIAL
+tutorial+

Clojure项目结构

最后,我们来谈谈Clojure项目的典型项目结构。 由于Clojure代码在Java虚拟机上运行,​​因此Clojure中的大多数项目结构与您在java项目中找到的类似。 以下是Eclipse中Clojure项目的示例项目结构的快照。

基本语法

关于上述程序结构,需要注意以下关键事项。

  • demo_1 - 这是放置Clojure代码文件的包。

  • core.clj - 这是主要的Clojure代码文件,它将包含Clojure应用程序的代码。

  • Leiningen文件夹包含运行任何基于Clojure的应用程序所需的文件,如clojure-1.6.0.jar。

  • pom.properties文件将包含诸如groupId,artifactId和Clojure项目版本之类的信息。

  • 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窗口

然后根据需要开始在REPL窗口中评估Clojure命令。

要在Eclipse中启动REPL会话,请单击“菜单”选项,转到“运行方式”→“Clojure应用程序”。

REPL会议

这将在一个单独的窗口中启动一个新的REPL会话以及控制台输出。

REPL控制台输出

从概念上讲,REPL类似于Secure 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变量来调用最后2个评估的表达式。

Clojure - Data Types

Clojure提供各种built-in data types.

(类型)

以下是Clojure中定义的数据类型列表。

  • Integers - 以下是Clojure中可用的整数表示。

    • Decimal Integers (Short, Long and Int) - 这些用于表示整数。 例如,1234。

    • Octal Numbers - 这些用于表示八进制表示中的数字。 例如,012。

    • Hexadecimal Numbers - 这些用于表示表示中的数字。 例如,0xff。

    • Radix Numbers - 这些用于表示基数表示中的数字。 例如,2r1111,其中基数是2到36之间的整数,包括2和36。

  • Floating point

    • 默认值用于表示32位浮点数。 例如,12.34。

    • 另一种表示形式是科学记数法。 例如,1.35e-12。

  • char - 这定义了单个字符文字。 使用间隙符号定义字符。 例如,/ e。

  • Boolean - 这表示一个布尔值,可以是true或false。

  • String - 这些是以String形式表示的文本文字。 例如,“Hello World”。

  • Nil - 这用于表示Clojure中的NULL值。

  • Atom - Atoms提供了一种管理共享,同步,独立状态的方法。 它们是引用类型,如refs和vars。

约束值

由于Clojure中的所有数据类型都是从Java继承的,因此有界值与Java编程语言中的有限值相同。 下表显示了数字和小数文字的最大允许值。

文字 范围
Short-32,768 to 32,767
int-2,147,483,648 to 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

例子 (Example)

以下程序显示了一个合并的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)

输出 (Output)

上述程序产生以下输出。

1
1.25
Hello

Clojure - Variables

在Clojure中, variables'def'关键字定义。 它有点不同,其中变量的概念更多地与绑定有关。 在Clojure中,值绑定到变量。 在Clojure中需要注意的一件事是变量是不可变的,这意味着为了使变量的值发生变化,需要将其销毁并重新创建。

以下是Clojure中的基本变量类型。

  • short - 用于表示短数字。 例如,10。

  • int - 用于表示整数。 例如,1234。

  • long - 用于表示长数字。 例如,10000090。

  • float - 用于表示32位浮点数。 例如,12.34。

  • char - 这定义了单个字符文字。 例如,'/ a'。

  • Boolean - 这表示一个布尔值,可以是true或false。

  • String - 这些是以String形式表示的文本文字。 例如,“Hello World”。

变量声明

以下是定义变量的一般语法。

语法 (Syntax)

(def var-name var-value)

其中'var-name'是变量的名称,'var-value'是绑定到变量的值。

例子 (Example)

以下是变量声明的示例。

(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)

命名变量 (Naming Variables)

变量的名称可以由字母,数字和下划线字符组成。 它必须以字母或下划线开头。 大写和小写字母是不同的,因为Clojure就像Java一样区分大小写的编程语言。

例子 (Example)

以下是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)

Note - 在上面的语句中,由于区分大小写,status和STATUS是Clojure中定义的两个不同的变量。

上面的示例显示了如何使用下划线字符定义变量。

打印变量 (Printing variables)

由于Clojure使用JVM环境,您还可以使用'println'函数。 以下示例显示了如何实现此目的。

例子 (Example)

(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)

输出 (Output)

上述程序产生以下输出。

1
1.25
Hello

Clojure - Operators

operator是一个符号,告诉编译器执行特定的数学或逻辑操作。

Clojure有以下类型的运算符 -

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 按位运算符

Note - 在Clojure中,运算符和操作数以下列语法方式工作。

语法 (Syntax)

(operator operand1 operand2 operandn)

例如,

例子 (Example)

(+ 1 2)

上面的例子对数字1和2进行了算术运算。

算术运算符 (Arithmetic Operators)

Clojure语言支持普通的算术运算符作为任何语言。 以下是Clojure中提供的算术运算符。

显示示例

操作者 描述
+ 增加了两个操作数 (+ 1 2)将给出3
从第一个减去第二个操作数 ( - 2 1)将给1
* 两个操作数的乘法 (* 2 2)将给出4
/ 由分母划分的分子 (float(/ 3 2))将给出1.5
inc 增量运算符用于将操作数的值递增1 公司5将给6
dec 增量运算符用于将操作数的值减1 12月5日将给4
max 返回其最大的参数 最大1 2 3将返回3
min 返回其最小的参数 min 1 2 3将返回1
rem 将第一个数除以第二个数的余数 rem 3 2将给出1

关系运算符 (Relational Operators)

关系运算符允许比较对象。 以下是Clojure中提供的关系运算符。

显示示例

操作者 描述
= 测试两个对象之间的相等性 (= 2 2)将给出真实
not= 测试两个对象之间的差异 (不是= 3 2)会给出真实的
< 检查左对象是否小于右操作数 (<2 3)将给出真实
<= 检查左对象是否小于或等于右操作数 (<= 2 3)将给出真实
> 检查左对象是否大于右操作数 (> 3 2)将给出真实
>= 检查左对象是否大于或等于右操作数 (> = 3 2)将给出真实

逻辑运算符 (Logical Operators)

逻辑运算符用于计算布尔表达式。 以下是Groovy中可用的逻辑运算符。

显示示例

操作者 描述
and 这是逻辑“和”运算符 (或者说真的)会给出真实的
or 这是逻辑“或”运算符 (而真假)会给出错误的
not 这是逻辑“非”运算符 (不是假的)会给出真实的

以下代码段显示了如何使用各种运算符。

按位运算符 (Bitwise Operators)

Clojure提供了四个按位运算符。 以下是Clojure中可用的按位运算符。

显示示例

Sr.No. 操作符和说明
1

bit-and

这是按位“和”运算符

2

bit-or

这是按位“或”运算符

3

bit-xor

这是按位“xor”或“Exclusive”或“运算符”

4

bit-not

这是按位否定运算符

以下是展示这些运算符的真值表。

p q p&Q p | q p ^ q
00000
01011
11110
10011

运算符优先级 (Operator Precedence)

与LISP一般情况一样,无需担心运算符优先级。 这是S-Expressions和前缀表示法的好处之一。 所有功能都从左到右,从里到外进行评估。 Clojure中的运算符只是函数,一切都是完全括号的。

Clojure - Loops

到目前为止,我们已经看到了以顺序方式一个接一个地执行的语句。 此外,Clojure中提供了一些语句,用于改变程序逻辑中的控制流。 然后将它们分类为控制语句流程,我们将详细介绍。

Sr.No. 循环和描述
1 While Statement

'while'语句通过首先计算条件表达式(布尔值)来执行,如果结果为true,则执行while循环中的语句。

2 Doseq声明

'doseq'语句类似于许多其他编程语言中的'for each'语句。 doseq语句基本上用于迭代序列。

3 Dotimes声明

'dotimes'语句用于执行语句'x'次。

4 循环声明

循环特殊形式不像'for'循环。 循环的用法与let绑定相同。 但是,循环设置递归点

Clojure - Decision Making

Decision-making structures要求程序员指定一个或多个要由程序评估或测试的条件,以及在条件被确定为真时要执行的语句,以及可选的其他语句,如果条件被确定为假。

Sr.No. 方法和描述
1 If Statement

在Clojure中,条件是一个表达式,它将其评估为真或假。 'If'条件为真,那么将执行语句#1,否则将执行语句#2。

2 If/do Expression

Clojure中的'if-do'表达式用于允许为'if'语句的每个分支执行多个表达式。

3 嵌套If语句

多个'if'语句互相嵌入。

4 Case Statement

Clojure提供了'case'语句,类似于Java编程语言中提供的'switch'语句。

5 Cond Statement

Clojure提供了另一种称为'cond'语句的评估声明。 该语句采用一组测试/表达式对。

Clojure - 函数

Clojure被称为函数式编程语言,因此您可能希望看到很多强调函数在Clojure中的工作方式。 本章介绍了Clojure中所有功能的完成。

Sr.No. 功能和描述
1 定义一个函数

通过使用'defn'宏来定义函数。

2 匿名函数

匿名函数是一个没有与之关联的名称的函数。

3 具有多个参数的函数

可以使用零个或多个参数定义Clojure函数。 传递给函数的值称为arguments ,参数可以是任何类型。

4 变量函数

Clojure提供了'case'语句,类似于Java编程语言中提供的'switch'语句。

5 高阶函数

高阶函数(HOF)是将其他函数作为参数的函数。 HOF是一种重要的函数式编程技术,在Clojure中非常常用。

Clojure - Numbers

Clojure中的Numbers数据类型派生自Java类。

Clojure支持整数和浮点数。

  • 整数是不包含分数的值。

  • 浮点数是包含小数部分的十进制值。

以下是Clojure中的数字示例。

(def x 5)
(def y 5.25)

其中'x'是Integer类型,'y'是float

在Java中,以下类附加到Clojure中定义的数字。

数字

要实际看到Clojure中的数字是从Java类派生的,请使用以下程序查看使用'def'命令时分配的数字类型。

例子 (Example)

(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'命令用于输出与分配给变量的值相关联的类。

输出 (Output)

上面的代码将产生以下输出。

Java.lang.long
Java.lang.double

数字测试

以下测试功能可用于数字。

Sr.No. 数字和描述
1 zero?

如果数字为零,则返回true,否则返回false。

2 pos?

如果number大于零,则返回true,否则返回false。

3 neg?

如果number小于零,则返回true,否则返回false。

4 even?

如果数字是偶数,则返回true,如果数字不是整数,则返回异常。

5 odd?

如果数字是奇数,则返回true,如果数字不是整数,则返回异常。

6 number?

如果数字实际上是数字,则返回true。

7 integer?

如果数字是整数,则返回true。

8 float?

如果数字是浮点数,则返回true。

Clojure - Recursion

我们在前面的主题中看到了recur语句,而'for'循环有点像循环, recur是Clojure中的一个真正的循环。

如果你有编程背景,你可能听说过尾递归,这是函数式语言的一个主要特性。 这种复现特殊形式是实现尾递归的形式。 正如单词“tail recursion”所示,必须在尾部位置调用recur。 换句话说,复发必须是最后评估的东西。

最简单的recur语句示例在'for'循环中使用。 在以下示例中,recur语句用于更改变量“i”的值,并将变量的值反馈回循环表达式。

例子 (Example)

(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)

输出 (Output)

上述程序产生以下输出。

0
1
2
3
4

Clojure - File I/O

在使用I/O时,Clojure提供了许多辅助方法。 它提供了更简单的类来为文件提供以下功能。

  • 读文件
  • 写入文件
  • 查看文件是文件还是目录

让我们来探讨一下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"]

写'到'文件

如果要编写'to'文件,可以使用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)

执行上面的代码时,Example.txt文件中将出现“hello world”行。 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 - Strings

通过将字符串文本括在引号中,在Clojure中构造字符串文字。 Clojure中的字符串需要使用双引号构建,例如“Hello World”。

例子 (Example)

以下是Clojure中字符串用法的示例。

(ns clojure.examples.hello
   (:gen-class))
(defn hello-world []
   (println "Hello World")
   (println "This is a demo application"))
(hello-world)

输出 (Output)

上述程序产生以下输出。

Hello World
This is a demo application

基本字符串操作

Clojure有许多可以在字符串上执行的操作。 以下是操作。

Sr.No. 字符串操作和描述
1 str

字符串的串联可以通过简单的str函数完成。

2 format

字符串的格式可以通过简单格式函数完成。 format函数使用java.lang.String.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 - Lists

List是用于存储数据项集合的结构。 在Clojure中,List实现了ISeq接口。 列表是使用list函数在Clojure中创建的。

例子 (Example)

以下是在Clojure中创建数字列表的示例。

(ns clojure.examples.example
   (:gen-class))
(defn example []
   (println (list 1 2 3 4)))
(example)

输出 (Output)

上面的代码产生以下输出。

(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中可用的列表方法。

Sr.No. 列表和说明
1 list*

创建一个包含其余项目的新列表,其中最后一个将被视为序列。

2 first

此函数返回列表中的第一项。

3 nth

此函数返回列表中“第n”个位置的项目。

4 cons

返回一个新列表,其中元素将添加到列表的开头。

5 conj

返回一个新列表,其中列表位于开头,而要追加的元素位于末尾。

6 rest

返回第一个项目后列表中的其余项目。

Clojure - Sets

Clojure中的集合是一组唯一值。 在set命令的帮助下,在Clojure中创建集合。

例子 (Example)

以下是在Clojure中创建集合的示例。

(ns clojure.examples.example
   (:gen-class))
(defn example []
   (println (set '(1 1 2 2))))
(example)

输出 (Output)

上面的代码产生以下输出。

#{1,2}

以下是Clojure中可用于集合的方法。

Sr.No. 集和描述
1 sorted-set

返回一组有序的元素。

2 get

返回索引位置的元素。

3 contains?

找出该集合是否包含某个元素。

4 conj

将元素追加到集合并返回新的元素集。

5 disj

从集合中分离元素。

6 union

返回一个输入集合并的集合

7 difference

返回第一组的集合,不包含其余集合的元素。

8 intersection

返回一组作为输入集的交集。

9 subset?

set1是set2的子集吗?

10 superset?

set1是set2的超集吗?

Clojure - Vectors

Vector是由连续整数索引的值的集合。 使用Clojure中的矢量方法创建矢量。

例子 (Example)

以下是在Clojure中创建矢量的示例。

(ns clojure.examples.example
   (:require [clojure.set :as set])
   (:gen-class))
(defn example []
   (println (vector 1 2 3)))
(example)

输出 (Output)

上面的代码产生以下输出。

[1 2 3]

以下是Clojure中可用的方法。

Sr.No. 矢量和描述
1 vector-of

创建单个基本类型't'的新向量,其中't'是以下之一:int:long:float:double:byte:short:char或:boolean。

2 nth

此函数返回向量中第n个位置的项目。

3 get

返回向量中索引位置的元素。

4 conj

将一个元素追加到向量并返回一组新的向量元素。

5 pop

对于列表或队列,返回没有第一个项目的新列表/队列,对于向量,返回没有最后一个项目的新向量。

6 subvec

从起始索引和结束索引返回子矢量。

Clojure - Maps

Map是将键映射到值的集合。 提供了两种不同的地图类型 - 散列和排序。 HashMaps需要正确支持hashCode和equals的密钥。 SortedMaps需要实现Comparable的键或SortedMaps的实例。

可以通过两种方式创建地图,第一种是通过哈希映射方法。

创作 - HashMaps

HashMaps具有典型的键值关系,并使用哈希映射函数创建。

(ns clojure.examples.example
   (:gen-class))
(defn example []
   (def demokeys (hash-map "z" "1" "b" "2" "a" "3"))
   (println demokeys))
(example)

输出 (Output)

上面的代码产生以下输出。

{z 1, b 2, a 3}

创作 - SortedMaps

SortedMaps具有基于关键元素对其元素进行排序的独特特征。 以下是一个示例,显示如何使用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}

从上面的程序中,您可以清楚地看到地图中的元素按键值排序。 以下是可用于地图的方法。

Sr.No. 地图和描述
1 get

如果key不存在,则返回映射到key,not-found或nil的值。

2 contains?

查看地图是否包含必需的密钥。

3 find

返回键的映射条目。

4 keys

返回地图中的键列表。

5 vals

返回地图中的值列表。

6 dissoc

从地图中分离键值条目。

7 merge

将两个映射条目合并为一个映射条目。

8 merge-with

返回一个映射,该映射由第一个映射的其余映射组成。

9 select-keys

返回一个映射,该映射仅包含键中键的那些条目。

10 rename-keys

将当前HashMap中的键重命名为新定义的键。

11 map-invert

反转地图,使值成为键,反之亦然。

Clojure - Namespaces

Clojure中的Namespaces用于将类区分为单独的逻辑空间,就像在Java中一样。 请考虑以下声明。

(:require [clojure.set :as set])

在上面的语句中,'clojure.set'是一个名称空间,它包含要在程序中使用的各种类和方法。 例如,上面的命名空间包含名为map-invert的函数,该函数用于反转键值的映射。 我们不能使用此函数,除非我们明确告诉我们的程序包含这个命名空间。

让我们看一下命名空间可用的不同方法。

Sr.No. 方法和描述
1 *ns*

这用于查看当前的命名空间。

2 ns

这用于创建新的命名空间并将其与正在运行的程序相关联。

3 alias

在当前名称空间中将别名添加到另一个名称空间。 参数是两个符号:要使用的别名和目标命名空间的符号名称。

4 all-ns

返回所有名称空间的列表。

5 find-ns

查找并返回特定的命名空间。

6 ns-name

返回特定命名空间的名称。

7 ns-aliases

返回与任何名称空间关联的别名。

8 ns-map

返回命名空间的所有映射的映射。

9 un-alias

返回一个映射,该映射仅包含键中键的那些条目。

Clojure - Exception Handling

任何编程语言都需要Exception handling来处理运行时错误,以便可以维护应用程序的正常流程。 异常通常会破坏应用程序的正常流程,这就是我们需要在应用程序中使用异常处理的原因。

例外大致分为以下几类 -

  • Checked Exception - 除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。

  • Unchecked Exception - 扩展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

Error

错误是无法恢复的,例如OutOfMemoryError,VirtualMachineError,AssertionError等。这些错误是程序永远无法从中恢复并导致程序崩溃的错误。 我们现在需要一些机制来捕获这些异常,以便在存在这些异常时程序可以继续运行。

下图显示了如何组织Clojure中的异常层次结构。 它都基于Java中定义的层次结构。

Clojure中的例外情况

捕捉异常

就像其他编程语言一样,Clojure提供了正常的“try-catch”块来捕获异常,以及它们何时发生。

以下是try-catch块的一般语法。

(try
   (//Protected code)
   catch Exception e1)
(//Catch block)

您可能引发异常的所有代码都放在Protected code block

catch block ,您可以编写自定义代码来处理异常,以便应用程序可以从异常中恢复。

让我们看看前面的例子,它生成了一个未找到文件的异常,看看我们如何使用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 block包含了错误的代码。 在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块捕获的,而不是普通的。

最后座

finally块遵循try块或catch块。 无论发生异常,最终都会执行最后一段代码。

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

从上面的程序中,您可以看到最后一个块也是在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 - Sequences

Sequences是在'seq'命令的帮助下创建的。 以下是序列创建的简单示例。

(ns clojure.examples.example
   (:gen-class))
;; This program displays Hello World
(defn Example []
   (println (seq [1 2 3])))
(Example)

上述程序产生以下输出。

(1 2 3)

以下是可用于序列的各种方法。

Sr.No. 方法和描述
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 - Regular Expressions

regular expression是用于在文本中查找子字符串的模式。 正则表达式用于各种编程语言,并在LISP类型编程语言中使用很多。

以下是正则表达式的示例。

//d+

上面的正则表达式用于在字符串中再找一个数字。 //字符用于确保字符“d”和“+”用于表示正则表达式。

通常,正则表达式适用于以下规则集。

  • 有两个特殊的位置字符用于表示一行的开头和结尾:插入符号(∧)和美元符号($):

  • 正则表达式还可以包括量词。 加号(+)表示一次或多次,应用于表达式的前一个元素。 星号(*)用于表示零次或多次出现。 问号(?)表示零或一次。

  • 元字符{和}用于匹配前一个字符的特定数量的实例。

  • 在正则表达式中,句点符号(。)可以表示任何字符。 这被描述为通配符。

  • 正则表达式可以包括字符类。 一组字符可以作为包含在元字符[和]中的简单字符序列给出,如[aeiou]中所示。 对于字母或数字范围,您可以使用[a-z]或[a-mA-M]中的短划线分隔符。 字符类的补码由方括号中的前导插入符号表示,如[∧a-z]中所示,表示除指定字符以外的所有字符。

以下方法可用于正则表达式。

Sr.No. 方法和描述
1 re-pattern

返回java.util.regex.Pattern的实例。 然后将其用于进一步的模式匹配方法。

2 refind

使用java.util.regex.Matcher.find()返回字符串到模式的下一个正则表达式匹配(如果有)

3 replace

replace函数用于用新的字符串值替换字符串中的子字符串。 通过使用模式来搜索子字符串。

4 replace-first

replace函数用于使用新的字符串值替换字符串中的子字符串,但仅用于第一次出现的子字符串。 通过使用模式来搜索子字符串。

Clojure - Predicates

Predicates是评估条件并提供true或false值的函数。 我们在数字章节的例子中看到了谓词函数。 我们看过像'even?'这样的功能 用来测试一个数字是否是偶数,或'neg?' 用于测试数字是否大于零。 所有这些函数都返回true或false值。

以下是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还为谓词提供了更多的函数。 以下方法可用于谓词。

Sr.No. 方法和描述
1 every-pred

获取一组谓词并返回一个函数'f',如果其所有组合谓词都针对其所有参数返回逻辑真值,则返回true,否则返回false。

2 every?

如果谓词对于每个值都为true,则返回true,否则返回false。

3 some

返回值集合中x的任何谓词值的第一个逻辑true值。

4 not-any?

如果集合中值的任何谓词在逻辑上为真,则返回false,否则返回true。

Clojure - Destructuring

Destructuring是Clojure中的一种功能,它允许人们从数据结构中提取值,例如向量,并将它们绑定到符号,而无需显式遍历数据结构。

让我们看看Destructuring究竟意味着什么以及它是如何发生的一个例子。

例子 (Example)

(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)

上述程序产生以下输出。

输出 (Output)

1 2 3 4

在上面的例子中,需要注意以下事项 -

  • 我们将整数向量定义为1,2,3和4。

  • 然后我们使用'let'语句直接将4个变量(a,b,c和d)分配给my-vector变量。

  • 如果我们对四个变量运行'println'语句,我们可以看到它们已经分别分配给向量中的值。

因此,当使用'let'语句分配时,clojure已经解构了my-vector变量,该变量有四个值。 然后将解构的四个值分配给四个参数。

如果有多余的变量没有可以分配给它们的相应值,那么它们将被赋值为nil。 以下示例清楚地说明了这一点。

例子 (Example)

(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。

输出 (Output)

1 2 3 4 nil

the-rest

'the-rest'变量用于存储剩余的值,这些值不能分配给任何变量。

以下程序显示了如何使用它的示例。

例子 (Example)

(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'变量。

输出 (Output)

1 2 (3 4)

解构地图

就像矢量一样,地图也可以被破坏。 以下是如何实现这一目标的示例。

例子 (Example)

(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的变量。

输出 (Output)

1 2

类似地,在向量的情况下,如果在解构发生时地图中没有对应的值,则该变量将被赋值为nil。

以下是一个例子。

例子 (Example)

(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)

上述程序产生以下输出。

输出 (Output)

1 2 nil

Clojure - Date and Time

由于Clojure框架是从Java类派生的,因此可以使用Clojure中Java中提供的日期时间类。 class date表示特定的时刻,精确到毫秒。

以下是日期时间类可用的方法。

java.util.Date

这用于在Clojure中创建日期对象。

语法 (Syntax)

以下是语法。

java.util.Date.

Parameters - 无。

Return Value - 分配Date对象并对其进行初始化,使其表示分配时间,测量精确到毫秒。

例子 (Example)

以下程序显示了如何使用它的示例。

(ns example)
(defn Example []
   (def date (.toString (java.util.Date.)))
   (println date))
(Example)

输出 (Output)

上述程序产生以下输出。 这将取决于运行程序的系统上的当前日期和时间。

Tue Mar 01 06:11:17 UTC 2016

java.text.SimpleDateFormat

这用于格式化日期输出。

语法 (Syntax)

以下是语法。

(java.text.SimpleDateFormat. format dt)

Parameters - 'format'是格式化日期时使用的格式。 'dt'是需要格式化的日期。

Return Value - 格式化的日期输出。

例子 (Example)

以下程序显示了如何使用它的示例。

(ns example)
(defn Example []
   (def date (.format (java.text.SimpleDateFormat. "MM/dd/yyyy") (new java.util.Date)))
   (println date))
(Example)

输出 (Output)

上述程序产生以下输出。 这将取决于运行程序的系统上的当前日期和时间。

03/01/2016

getTime

返回自此Date对象表示的1970年1月1日00:00:00 GMT以来的毫秒数。

语法 (Syntax)

以下是语法。

(.getTime)

Parameters - 无。

Return Value - 自该日期起由1970年1月1日00:00:00 GMT起的毫秒数。

例子 (Example)

以下程序显示了如何使用它的示例。

(ns example)
(import java.util.Date)
(defn Example []
   (def date (.getTime (java.util.Date.)))
   (println date))
(Example)

输出 (Output)

上述程序产生以下输出。 这将取决于运行程序的系统上的当前日期和时间。

1456812778160

Clojure - Atoms

Atoms是Clojure中的一种数据类型,它提供了一种管理共享,同步,独立状态的方法。 原子就像任何其他编程语言中的任何引用类型一样。 原子的主要用途是保存Clojure的不可变数据结构。 原子持有的值随swap! method而改变swap! method swap! method

在内部,交换! 读取当前值,将函数应用于它,并尝试对其进行比较和设置。由于另一个线程可能在中间时间内更改了值,因此可能必须重试,并在旋转循环中执行此操作。 实际效果是,该值始终是将所提供函数应用于当前值的结果。

例子 (Example)

原子是在原子方法的帮助下创建的。 以下程序中显示了相同的示例。

(ns clojure.examples.example
   (:gen-class))
(defn example []
   (def myatom (atom 1))
   (println @myatom))
(example)

输出 (Output)

上述程序产生以下结果。

1

使用@符号访问atom的值。 Clojure有一些可以对原子执行的操作。 以下是操作。

Sr.No. 操作和描述
1 reset!

将atom的值设置为新值,而不考虑当前值。

2 compare-and-set!

当且仅当原子的当前值与原子持有的旧值相同时,原子上将atom的值设置为新值。 如果set发生则返回true,否则返回false。

3 swap!

原子地将原子的值与基于特定函数的新值进行交换。

Clojure - Metadata

在Clojure中, metadata用于注释集合中的数据或存储在符号中的数据。 这通常用于向底层编译器注释有关类型的数据,但也可用于开发人员。 元数据不被视为对象价值的一部分。 同时,元数据是不可变的。

Clojure中有关元数据的以下操作是可能的。

Sr.No. 操作和描述
1 meta-with

此函数用于为任何对象定义元数据映射。

2 meta

此函数用于查看是否有任何元数据与对象关联。

3 vary-meta

返回与原始对象具有相同类型和值的对象,但具有组合的元数据。

Clojure - StructMaps

StructMaps用于在Clojure中创建结构。 例如,如果要创建由Employee Name和Employeeid组成的结构,则可以使用StructMaps执行此操作。

关于StructMaps,Clojure中可以执行以下操作。

Sr.No. 操作和描述
1 defstruct

该函数用于定义所需的结构。

2 struct

此函数用于定义类型的结构对象,该结构对象由defstruct操作创建。

3 struct-map

此函数用于通过显式定义将哪些值分配给结构中的哪些键来专门为键值指定值。

4 Accessing Individual Fields

可以通过访问键和结构对象来访问结构的各个字段。

5 Immutable Nature

默认情况下,结构也是不可变的,因此如果我们尝试更改特定键的值,它将不会更改。

6 在结构中添加新密钥

由于结构是不可变的,因此可以通过创建新结构将另一个键添加到结构的唯一方法。 以下程序显示了如何实现这一目标的示例。

Clojure - Agents

正如多次指出的那样,Clojure是一种编程语言,其中许多数据类型是不可变的,这意味着可以改变变量值的唯一方法是创建一个新变量并为其赋值。 但是,Clojure确实提供了一些可以创建可变状态的元素。 我们已经看到这可以用atom数据类型实现。 另一种可以实现的方式是通过代理商。

Agents提供各个位置的独立,异步更改。 代理在其生命周期中绑定到单个存储位置,并且仅允许由于操作而发生该位置的突变(到新状态)。 操作是异步应用于代理状态并且其返回值成为代理的新状态的函数(可选地,附加参数)。

关于代理,Clojure可以执行以下操作。

Sr.No. 操作和描述
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

Watchers是添加到变量类型的函数,例如原子和引用变量,当变量类型的值发生变化时,它们会被调用。 例如,如果调用程序更改了atom变量的值,并且如果将watcher函数附加到atom变量,则只要原子的值发生更改,就会调用该函数。

Clojure for Watchers提供以下功能。

add-watch

向watch/atom/var/ref引用添加监视功能。 手表'fn'必须是4个参数的'fn':一个键,一个参考,它的旧状态,它的新状态。 每当参考的状态可能已被更改时,任何已注册的手表都将调用其功能。

语法 (Syntax)

以下是语法。

(add-watch variable :watcher
   (fn [key variable-type old-state new-state]))

Parameters - 'variable'是原子或引用变量的名称。 'variable-type'是变量的类型,原子或引用变量。 'old-state&new-state'是将自动保存变量的旧值和新值的参数。 'key'必须是每个参考的唯一,并可用于删除手表与删除手表。

Return Value - 无。

例子 (Example)

以下程序显示了如何使用它的示例。

(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)

输出 (Output)

上述程序产生以下输出。

The value of the atom has been changed
old-state 0
new-state 2

remove-watch

移除已附加到参考变量的手表。

语法 (Syntax)

以下是语法。

(remove-watch variable watchname)

Parameters - 'variable'是原子或引用变量的名称。 'watchname'是定义手表功能时给手表的名称。

Return Value - 无。

例子 (Example)

以下程序显示了如何使用它的示例。

(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)

输出 (Output)

上述程序产生以下输出。

The value of the atom has been changed
old-state 0
new-state 2

您可以从上面的程序中清楚地看到,第二个重置命令不会触发观察者,因为它已从观察者列表中删除。

Clojure - Macros

在任何语言中, Macros都用于生成内联代码。 Clojure也不例外,为开发人员提供简单的宏设施。 宏用于编写代码生成例程,这为开发人员提供了一种根据开发人员的需求定制语言的强大方法。

以下是可用于宏的方法。

defmacro

此函数用于定义宏。 宏将具有宏名称,参数列表和宏的主体。

语法 (Syntax)

以下是语法。

(defmacro name [params*] body)

Parameters - 'name'是宏的名称。 'params'是分配给宏的参数。 'body'是宏的主体。

Return Value - 无。

例子 (Example)

以下程序显示了如何使用它的示例。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (defmacro Simple []
      (println "Hello"))
   (macroexpand '(Simple)))
(Example)

输出 (Output)

上述程序产生以下输出。

Hello

从上面的程序中你可以看到宏'Simple'被内联扩展为'println'“Hello”。 宏与函数类似,唯一的区别是在宏的情况下评估表单的参数。

macro-expand

这用于扩展宏并将代码内嵌在程序中。

语法 (Syntax)

以下是语法。

(macroexpand macroname)

Parameters - 'macroname'是需要扩展的宏的名称。

Return Value - 扩展的宏。

例子 (Example)

以下程序显示了如何使用它的示例。

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (defmacro Simple []
      (println "Hello"))
   (macroexpand '(Simple)))
(Example)

输出 (Output)

上述程序产生以下输出。

Hello

带参数的宏

宏也可用于接受参数。 宏可以接受任意数量的参数。 下面的示例展示了如何使用参数。

例子 (Example)

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (defmacro Simple [arg]
      (list 2 arg))
   (println (macroexpand '(Simple 2))))
(Example)

上面的示例在Simple宏中放置一个参数,然后使用该参数将参数值添加到列表中。

输出 (Output)

上述程序产生以下输出。

(2 2)

Clojure - Reference Values

Reference values是Clojure可以使用变量变量的另一种方式。 Clojure提供可变数据类型,例如原子,代理和引用类型。

以下是可用于参考值的操作。

Sr.No. 操作和描述
1 ref

这用于创建参考值。 创建参考值时,可以选择提供验证器功能,该功能将验证创建的值。

2 ref-set

此函数用于设置对新值的引用值,而不管旧值是什么。

3 alter

此函数用于以安全的方式更改引用类型的值。 这是在一个线程中运行,其他进程无法访问。

4 dosync

在包含表达式和任何嵌套调用的事务中运行表达式(在隐式do中)。

5 commute

Commute也用于更改引用类型的值,就像alter和ref-set一样。

Clojure - Databases

为了使用数据库功能,请确保首先从以下URL下载jdbc files - https://codeload.github.com/clojure/java.jdbc/zip/master

您将找到一个zip文件,其中包含Clojure可以连接到数据库的必要驱动程序。 解压缩zip文件后,请确保将解压缩的位置添加到类路径中。

数据库连接的主文件是位于clojure/java中的名为jdbc.clj的文件。

clojure jdbc连接器支持各种各样的数据库,其中一些数据库如下。

  • H2Database
  • Oracle
  • Microsoft SQL Server
  • MySQL
  • PostgreSQL

在我们的示例中,我们将使用MySQL DB作为示例。

关于数据库,Clojure可以进行以下操作。

数据库连接 (Database Connection)

在连接MySQL数据库之前,请确保以下内容 -

  • 您已经创建了一个数据库TESTDB。

  • 您已在TESTDB中创建了一个表EMPLOYEE。

  • 此表包含FIRST_NAME,LAST_NAME,AGE,SEX和INCOME字段。

  • 用户ID“testuser”和密码“test123”设置为访问TESTDB。

  • 确保已下载“mysql jar文件”并将该文件添加到类路径中。

  • 您已经通过MySQL教程来了解MySQL基础知识

语法 (Syntax)

以下是在Clojure中创建连接的语法。

(def connection_name {
   :subprotocol “protocol_name”
   :subname “Location of mysql DB”
   :user “username” :password “password” })

Parameters - 'connection_name'是要为连接指定的名称。 'subprotocol'是用于连接的协议。 默认情况下,我们将使用mysql协议。 'subname'是连接到mysql数据库的URL以及数据库名称。 'user'是用于连接数据库的用户名。 'password'是用于连接数据库的密码。

Return Value - 这将提供一个连接字符串,可用于后续的mysql操作。

以下示例显示如何连接到信息架构中的表并检索表中的所有数据。

例子 (Example)

(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查询数据的语法。

语法 (Syntax)

clojure.java.jdbc/query dbconn
["query"]
   :row-fn :sequence

Parameters - 'dbconn'是用于连接数据库的连接的名称。 'query'是用于从数据库中获取数据的查询字符串。 ':sequence'默认为从数据库中提取的所有数据行,并作为序列返回。 然后可以对序列进行必要的操作以查看已获取的数据。

Return Value - 这将返回一个序列,该序列将包含查询操作中的数据行。

以下示例显示如何连接到employee表并获取表中行的first_name列。

例子 (Example)

(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是序列,它是作为获取操作的结果返回的。

如果我们假设我们的数据库中只有一行包含John的first_name值,则以下将是上述程序的输出。

(John)

插入数据

当您想要将记录创建到数据库表中时,它是必需的。 以下是使用Clojure插入数据的语法。 这是通过使用'insert!'来完成的'insert!' 功能。

语法 (Syntax)

clojure.java.jdbc/insert!
   :table_name {:column_namen columnvalue}

Parameters - ':table_name'是需要进行插入的表的名称。 '{:column_namen columnvalue}'是所有列名称和值的映射,需要在表中作为一行添加。

Return Value - 如果插入成功,则返回nil。

以下示例显示如何将记录插入testdb数据库中的employee表。

例子 (Example)

(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!'从表中'delete!' 功能。 以下是有关如何执行此操作的语法。

语法 (Syntax)

clojure.java.jdbc/delete!
   :table_name [condition]

Parameters - ':table_name'是需要进行插入的表的名称。 'condition'是用于确定需要从表中删除哪一行的条件。

Return Value - 这将返回已删除的行数。

以下示例说明如何从testdb数据库中的employee表中删除记录。 该示例根据年龄等于30的条件从表中删除一行。

例子 (Example)

(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!'从表中'update!' 功能。 以下是有关如何执行此操作的语法。

语法 (Syntax)

clojure.java.jdbc/update!
   :table_name
{setcondition}
[condition]

Parameters - ':table_name'是需要进行插入的表的名称。 'setcondition'是需要根据地图提到的更新的列。 'condition'是用于确定需要从表中删除哪一行的条件。

Return Value - 这将返回更新的行数。

以下示例说明如何从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。

事务 (Transactions)

事务是确保数据一致性的机制。 交易具有以下四个属性 -

  • Atomicity - 事务完成或根本没有任何事情发生。

  • Consistency - 事务必须以一致状态启动,并使系统保持一致状态。

  • Isolation - 在当前事务之外不可见事务的中间结果。

  • Durability - 提交事务后,即使系统出现故障,影响也会持续存在。

例子 (Example)

以下示例显示如何在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 Interface

我们已经知道,Clojure代码最终在Java虚拟环境中运行。 因此,只有Clojure能够利用Java的所有功能才有意义。 在本章中,我们将讨论Clojure和Java之间的关联。

调用Java方法

可以使用点表示法调用Java方法。 一个例子是字符串。 由于Clojure中的所有字符串都是Java字符串,因此可以在字符串上调用普通的Java方法。

有关如何完成此操作的示例,请参见以下程序。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (println (.toUpperCase "Hello World")))
(Example)

上述程序产生以下输出。 您可以从代码中看到,如果您只是为任何字符串方法调用点表示法,它也可以在Clojure中使用。

输出 (Output)

HELLO WORLD

使用参数调用Java方法

您还可以使用参数调用Java方法。 有关如何完成此操作的示例,请参见以下程序。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (println (.indexOf "Hello World","e")))
(Example)

上述程序产生以下输出。 您可以从上面的代码中看到,我们将参数“e”传递给indexOf方法。 上述程序产生以下输出。

输出 (Output)

1

创建Java对象

可以使用类似于Java中的'new'关键字在Clojure中创建对象。

有关如何完成此操作的示例,请参见以下程序。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (def str1 (new String "Hello"))
   (println str1))
(Example)

上述程序产生以下输出。 您可以从上面的代码中看到,我们可以使用'new'关键字从Java的现有String类创建一个新对象。 我们可以在创建对象时传递值,就像我们在Java中一样。 上述程序产生以下输出。

输出 (Output)

Hello

下面是另一个示例,它显示了如何创建Integer类的对象并在常规Clojure命令中使用它们。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (def my-int(new Integer 1))
   (println (+ 2 my-int)))
(Example)

上述程序产生以下输出。

输出 (Output)

3

Import Command

我们还可以使用import命令在命名空间中包含Java库,以便可以轻松访问类和方法。

以下示例显示了我们如何使用import命令。 在示例中,我们使用import命令从java.util.stack库中导入类。 然后我们可以按原样使用堆栈类的push和pop方法。

例子 (Example)

(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)

上述程序产生以下输出。

输出 (Output)

First Element

使用Java命令运行代码

可以使用Java命令运行Clojure代码。 以下是如何完成此操作的语法。

java -jar clojure-1.2.0.jar -i main.clj

您必须提及Clojure jar文件,以便所有基于Clojure的类都将加载到JVM中。 'main.clj'文件是需要执行的Clojure代码文件。

Java内置函数 (Java Built-in Functions)

Clojure可以使用Java的许多内置函数。 其中一些是 -

Math PI function - Clojure可以使用Math方法得到PI的值。 以下是示例代码。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (println (. Math PI)))
(Example)

上面的代码产生以下输出。

输出 (Output)

3.141592653589793

System Properties - Clojure还可以查询系统属性。 以下是示例代码。

例子 (Example)

(ns Project
   (:gen-class))
(defn Example []
   (println (.. System getProperties (get "java.version"))))
(Example)

根据系统上Java的版本,将显示相应的值。 以下是输出示例。

输出 (Output)

1.8.0_45

Clojure - Concurrent Programming

在Clojure编程中,大多数数据类型都是不可变的,因此当涉及并发编程时,使用这些数据类型的代码在代码在多个处理器上运行时非常安全。 但很多时候,需要共享数据,当涉及跨多个处理器的共享数据时,有必要确保在使用多个处理器时保持数据的完整性状态。 这称为concurrent programming ,Clojure为此类编程提供支持。

通过dosync,ref,set,alter等公开的软件事务存储器系统(STM)支持以同步和协调的方式在线程之间共享改变状态。 代理系统支持以异步和独立方式共享线程之间的更改状态。 原子系统支持以同步和独立的方式共享线程之间的变化状态。 而通过def,绑定等公开的动态var系统支持隔离线程内的变化状态。

其他编程语言也遵循并行编程的模型。

  • 它们直接引用可以更改的数据。

  • 如果需要共享访问,则锁定对象,更改值,并继续进行下一次访问该值的过程。

在Clojure中没有锁,但间接引用不可变的持久数据结构。

Clojure中有三种类型的引用。

  • Vars - Vars在线程中被隔离。

  • Refs - 线程之间的变化是同步和协调的。

  • Agents - 涉及线程之间的异步独立更改。

关于并发编程,在Clojure中可以进行以下操作。

事务 (Transactions)

Clojure中的并发性基于事务。 引用只能在事务中更改。 以下规则适用于交易。

  • 所有变化都是原子的和孤立的。
  • 对引用的每次更改都发生在事务中。
  • 没有交易看到另一个交易产生的影响。
  • 所有事务都放在dosync块中。

我们已经看到了dosync块的功能,让我们再看看它。

dosync

在包含表达式和任何嵌套调用的事务中运行表达式(在隐式do中)。 如果此线程上尚未运行任何事务,则启动事务。 任何未捕获的异常都将中止事务并从dosync流出。

以下是语法。

语法 (Syntax)

(dosync expression)

Parameters - 'expression'是将出现在dosync块中的表达式集。

Return Value - 无。

让我们看一个例子,其中我们尝试改变参考变量的值。

例子 (Example)

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   (alter names conj "Mark"))
(Example)

输出 (Output)

运行时上述程序会出现以下错误。

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块中,如下面的程序所示。

例子 (Example)

(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)

上述程序产生以下输出。

输出 (Output)

[John Mark]

让我们看一下dosync的另一个例子。

例子 (Example)

(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块中被更改。 如果事务成功,则两个值都将更改,否则整个事务将失败。

上述程序产生以下输出。

输出 (Output)

10 20
-10 40

Clojure - Applications

Clojure有一些贡献的库,可以创建DesktopWeb-based applications 。 我们来讨论它们中的每一个。

Sr.No. 应用和描述
1 桌面 - 跷跷板

See-saw是一个可用于创建桌面应用程序的库。

2 桌面 - 更改文本的值

可以使用'config!'更改窗口中内容的值。 选项。 在以下示例中配置! 选项用于将窗口内容更改为“Good Bye”的新值。

3 桌面 - 显示模态对话框

可以使用see-saw类的alert方法显示模式对话框。 该方法采用文本值,需要在模态对话框中显示。

4 桌面 - 显示按钮

可以在按钮类的帮助下显示按钮。

5 桌面 - 显示标签

可以在标签类的帮助下显示标签。

6 桌面 - 显示文本字段

可以在文本类的帮助下显示文本字段。

Web应用程序 - 简介

要在Clojure中创建Web应用程序,您需要使用Ring应用程序库,该库可从以下链接获得https://github.com/ring-clojure/ring

您需要确保从站点下载必要的jar并确保将其添加为Clojure应用程序的依赖项。

Ring framework提供以下功能 -

  • 进行设置,使得http请求作为常规Clojure HashMap进入Web应用程序,同样使得它可以将响应作为HashMap返回。

  • 提供准确描述这些请求和响应映射应该是什么样的规范。

  • 沿着Web服务器(Jetty)运行并将Web应用程序连接到它。

Ring框架自动启动Web服务器并确保Clojure应用程序在此服务器上运行。 然后还可以使用Compojure框架。 这允许人们创建路线,这是现在大多数现代Web应用程序的开发方式。

Creating your first Clojure application - 以下示例说明如何在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加载,因此如果您浏览到此位置,您将收到以下输出。

Clojure应用程序

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函数一样简单。 (获取“/ Tutorial”[]“这是关于Clojure的教程”)

如果浏览到位置http://localhost:3000/Tutorial ,您将收到以下输出。

本地主机

Clojure - Automated Testing

在本章中,我们将讨论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”字符串是给测试用例的名称。

  • 应该和不应该被称为assertions 。 所有断言都以should开头。 应该和不应该只是众多断言中的两个。 他们都表达了他们将分别检查truthy-ness和falsy-ness的表达。

如果运行测试用例,您将获得以下输出。 输出显示测试用例运行所用的时间(以毫秒为单位)。

←[32m.←[0m←[32m.←[0m
Finished in 0.00014 seconds

测试基于Web的应用程序

Selenium是用于测试现代基于Web的应用程序的关键框架之一。 还提供了Clojure库,可用于测试基于Web的应用程序。

让我们看看如何使用Selenium库来测试Clojure基于Web的应用程序。

Step 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))

Step 2 - 接下来确保下载selenium jar文件https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-server/2.47.0并将其包含在类路径中。

Step 3 - 还要确保下载'clj'Web驱动程序,该驱动程序将用于从以下位置运行Web测试。

https://clojars.org/clj-webdriver/versions/0.7.1

Step 4 - 在项目目录中,创建另一个名为features的目录,并创建一个名为'config.clj'的文件。

Step 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 http://localhost:3000加载

Step 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 - Libraries

使Clojure库如此强大的一件事是可用于Clojure框架的库的数量。 我们已经看到在我们之前的示例中使用了很多库,用于Web测试,Web开发,开发基于swing的应用程序,用于连接MySQL数据库的jdbc库。 以下只是几个库的几个例子。

data.xml

该库允许Clojure使用XML数据。 要使用的库版本是org.clojure/data.xml“0.0.8”。 data.xml支持解析和发出XML。 解析函数将从Reader或InputStream读取XML。

例子 (Example)

以下是从字符串到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”。

例子 (Example)

以下是使用此库的示例。

(ns clojure.examples.example
   (:require [clojure.data.json :as json])
   (:gen-class))
(defn Example []
   (println (json/write-str {:a 1 :b 2})))
(Example)

输出 (Output)

上述程序产生以下输出。

{\"a\":1,\"b\":2}

data.csv

该库允许Clojure使用'csv'数据。 要使用的库版本是org.clojure/data.csv“0.1.3”。

例子 (Example)

以下是使用此库的示例。

(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.的文件中out-file.csv.

↑回到顶部↑
WIKI教程 @2018