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 Overview

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 − 啟動安裝程式並點選“下一步”按鈕。

Launching Installer

步驟 3 − 指定安裝位置並點選“下一步”按鈕。

Location Installation

步驟 4 − 安裝程式將檢測現有 Java 安裝的位置。點選“下一步”按鈕繼續。

Java Installation

步驟 5 − 點選“安裝”按鈕開始安裝。

Begin Installation

安裝完成後,它將為您提供開啟 Clojure REPL 的選項,這是一個可用於建立和測試 Clojure 程式的環境。

Clojure Programs

Eclipse 安裝

在繼續安裝之前,請確保滿足以下系統要求。

系統要求

JDK JDK 1.7 或更高版本
Eclipse Eclipse 4.5 (Mars)

步驟 1 − 開啟 Eclipse 並點選選單項。點選幫助 → Eclipse 市場。

Eclipse Marketplace

步驟 2 − 在出現的對話方塊中輸入關鍵字 Clojure 並點選“Go”按鈕。將出現 CounterClockwise 選項,點選“安裝”按鈕開始安裝此外掛。

Clojure Dialog Box

步驟 3 − 在下一個對話方塊中,點選“確認”按鈕開始安裝。

Clojure Confirm Button

步驟 4 − 在下一個對話方塊中,您將被要求接受許可協議。接受許可協議並點選“完成”按鈕繼續安裝。

Accept License Agreement

安裝將開始,完成後,它將提示您重新啟動 Eclipse。

重新啟動 Eclipse 後,您將在 Eclipse 中看到建立新 Clojure 專案的選項。

Clojure Project

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 專案的示例專案結構快照。

Basic Syntax

關於上述程式結構,需要注意以下關鍵事項:

  • 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 Window

然後根據需要開始在 REPL 視窗中評估 Clojure 命令。

要在 Eclipse 中啟動 REPL 會話,請點選選單選項,轉到“以...方式執行”→“Clojure 應用程式”。

REPL Session

這將在一個單獨的視窗中啟動一個新的 REPL 會話以及控制檯輸出。

REPL Console Output

從概念上講,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 中定義的數字相關聯。

Numbers

要實際檢視 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中定義的繼承結構。

Exceptions in Clojure

捕獲異常

與其他程式語言一樣,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,因此如果您瀏覽到此位置,您將收到以下輸出。

Clojure Application

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://:3000/Tutorial,您將收到以下輸出。

Localhost

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://: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` 的檔案。

廣告