Clojure

Clojure（ər）是Lisp編程語言在Java平臺上的現代、動態及函數式方言。 與其他Lisp一樣，Clojure視代碼為數據且擁有一套Lisp宏系統。Clojure的開發過程目前由社區驅動，其作者裡奇·希基則以終身仁慈獨裁者的身份監督。
Clojure提倡不可變性與持久數據結構並鼓勵程序員顯式地管理標識及其狀態。對利用不可變值及顯式時間進展構造（explicit progression-of-time constructs）進行編程的專注旨在促進更加健壯的（尤其是併發）程序的開發。Clojure的類型系統是完全動態的，但人們近期也開始探索其基於漸進類型化的實現。
對Clojure的商業支持由Cognitect公司提供。 每年都會在全球範圍內舉辦年度Clojure會議，其中最著名的是Clojure/conj。

**歷史與開發過程**
裡奇·希基創造了Clojure語言。此前，他開發過類似但基於.NET平臺的項目——dotLisp。在那之前，他還嘗試了三次在Lisp與Java之間提供互操作：Common Lisp的Java外語接口、Lisp的外語對象接口以及Lisp友好的Java Servlet接口。
在公開發布之前，裡奇·希基花了大約兩年半的時間開發Clojure。他在沒有外部資金的情況下，將其大部分時間都專門投入到了Clojure的工作上。開發快要完成，裡奇·希基向Common Lisp社區裡的一些朋友發佈電子郵件，宣佈他完成開發了Clojure。
Clojure的開發過程在Clojure JIRA項目網頁由社區驅動並管理。該網站可用於提交問題報告。一般的開發討論是在Clojure谷歌網上論壇上進行的。 任何人都可以提交錯誤報告和想法，而貢獻補丁前則需要先簽署Clojure貢獻者協議。 JIRA錯誤報告由一組篩選者處理，最終由裡奇·希基批准更改。

**設計理念**
裡奇·希基開發Clojure的原因是因為他想要一款適合函數式編程的現代Lisp。該語言既需要與已建立的Java平臺共生又需要有適合併發性的設計。
在Clojure中，狀態以標識的概念為特徵。一系列狀態隨著時間的推移而產生，就是標識。由於狀態是不可變的值，任意數量的工作單位都可以在其上並行實施操作，併發性就成為一道管理狀態到狀態的變化的問題。因此，Clojure提供了幾個可變的引用類型。每個引用類型都有其明確定義的語義用於控制狀態之間的躍遷。

**語言概述**
Clojure執行於Java平臺之上，因此，與Java緊密集成並完全支持從Clojure調用Java代碼。 與此同時，也可以從Java調用Clojure代碼。 Leiningen是社區中普遍使用的項目自動化工具。Leiningen為Maven集成提供支持，處理項目包管理和依賴項。Leiningen的配置使用的則是Clojure語法。
與其他大多數Lisp一樣，Clojure的語法建立在S-表達式之上。S-表達式在被編譯之前先由讀取器解析為數據結構。 除了列表之外，Clojure的讀取器還支持映射、集合及向量等的字面量語法。這些字面量隨後會被直接編譯成上述數據結構。 Clojure是Lisp-1且有一套與其它Lisp不兼容的數據結構，因此，Clojure不支持與Lisp的其它方言之間的代碼級兼容性。
作為一門Lisp方言，函數在Clojure中是一等公民。此外，Clojure還支持讀取﹣求值﹣輸出循環以及一套宏系統。 Clojure的Lisp宏系統與Common Lisp的系統極為相似。唯一不同的是，Clojure的重音符（稱為語法引用）用名字空間來限定符號。這有助於防止意外的名字捕獲，因為Clojure禁止綁定到用名字空間限定的名字（namespace-qualified name）上。如果需要強制捕獲宏擴展（capturing macro expansion，）那麼就需要顯示地完成該過程。Clojure不支持用戶定義的讀取器宏（reader macro，）但Clojure的讀取器支持更具約束力的語法擴展形式。 Clojure支持多方法（multimethods。） 對於類似接口的抽象，Clojure提供基於協議的多態性以及基於記錄的數據類型系統。 Clojure通過這些設計來提供高性能且動態的多態性以避免所謂的“表達式問題”（"expression problem"。）
Clojure支持惰性序列，並鼓勵不可變性與持久數據結構（persistent data structure。）Clojure作為一門函數式編程語言將重點放在遞歸與高階函數上而不是基於副作用的循環流程上。Clojure不支持自動尾調用優化，因為JVM還不支持該項優化，但是，可以用recur關鍵字顯式地執行該項優化。 對於並行與併發計算，Clojure提供軟件事務內存、 響應式代理系統及基於通道的併發編程。
Clojure 1.7引入了讀取器條件表達式從而允許在同一名字空間中嵌入Clojure與ClojureScript代碼。 變換歸納器的加入則提供了另一種組合變換的方法。變換歸納器可以使高階函數（如，map和fold）更加抽象從而使之獨立於其輸入數據源。傳統地說，這些函數一般被應用於序列上，而變換歸納器允許這些函數被應用於通道上並讓用戶定義她們自己的變換歸納模型。

**平臺**
Clojure的主要平臺是Java但也存在其他目標平臺上的實現。其中，最值得關注的是ClojureScript（可被編譯成ECMAScript 3）和ClojureCLR（.NET平臺上的完整移植版，可與其生態系統互操作。）2013年對1,060名受訪者進行的Clojure社區調查發現，47％的受訪者在使用Clojure的同時也使用ClojureScript。2014年，這一數字增長到了55％，而到了2015年，則達到了66％（根據2,445名受訪者）。 人氣較高的ClojureScript項目包括React實現，如Reagent、re-frame、Rum及Om。

**人氣**
隨著對函數式編程的興趣的持續升溫，Clojure也越來越多地受到Java平臺上的軟件開發人員的青睞。該語言也一度成為知名軟件開發老將的首選或推薦語言，如Brian Goetz、Eric Evans、詹姆斯·高斯林、保羅·格雷厄姆及Robert C. Martin（俗稱“鮑勃大叔”）等人。
在由Snyk和Java Magazine合作編寫的“JVM生態系統報告2018”（據稱是“Java開發人員有史以來規模最大的調查”）中，Clojure被評為用於“主要應用程序”的第二大人氣編程語言（僅次於Java）。
業內使用Clojure的公司有蘋果公司、Atlassian、Funding Circle、Netflix、 Puppet 、沃爾瑪及其他大型軟件公司與美國國家航空航天局等政府機構。Clojure也一度被用於創意計算，包括視覺藝術、音樂、遊戲和詩歌。
美國知名軟件諮詢公司ThoughtWorks在為其“技術雷達”評估函數式編程語言時表達了他們對Clojure的青睞，稱其為“Lisp在JVM上的簡單及優雅實現”，並在2012年將其狀態提升為“採用”（“ADOPT”）。
越來越多的非官方和/或實驗性的其他平臺實現也驗證了該語言的人氣：- CljPerl：Clojure的Perl實現- Clojerl：BEAM（Erlang虛擬機）上的Clojure- clojure-py：Clojure的純Python實現- Ferret：可被編譯成運行於微控制器的自包含（self-contained）C++11- Joker：用Go實現的解釋器和linter- Las3r：執行於ActionScript虛擬機（Adobe Flash Player平臺）的Clojure子集- Pixie：受Clojure啟發並用RPython實現的Lisp方言- Rouge：Clojure基於YARV的Ruby實現

**開發工具**
Clojure的開發工具在近幾年得到了顯著的改善。以下是目前最具人氣的集成開發環境/編輯器及其Clojure插件。這些工具的結合為Clojure編程提供了出色的支持。
除了社區提供的開發工具之外，官方的命令行界面工具也隨著Clojure 1.9一起發佈並可在GNU/Linux、macOS及Windows上使用。

**功能示例**
以下示例均可在Clojure REPL中運行（如，使用Clojure命令行界面工具啟動的REPL或在REPL.it上提供的在線REPL。）

**簡單性**
由於強調簡單性，典型的Clojure程序主要包括函數和簡單的數據結構（即列表，向量，映射和集合）：
; 一個典型的Clojure程序的入口
(defn -main ; 函數名
& args ; 參數向量 (`&`表示可變參數)
(println "Hello, World!")) ; 函數體

**REPL編程**
與其他Lisp一樣，Clojure的標誌性特徵之一是基於REPL的交互式編程。在以下示例中，;;表示一行註釋的開始，而;; =>則表示輸出：
; 定義一個var
(def a 42)
; => #'user/a
; 調用名為`+`的函數
(+ a 8)
; => 50
; 調用名為`even?`的函數
(even? a)
; => true
; 定義一個函數以返回n除10之餘
(defn foo n (rem n 10))
; => #'user/foo
; 調用該函數
(foo a)
; => 2
; 打印`rem`的文檔字符串（docstring）
(doc rem)
; =>
-------------------------
clojure.core/rem
(num div)
remainder of dividing numerator by denominator.
; 打印`rem`的源代碼
(source rem)
; =>
(defn rem
"remainder of dividing numerator by denominator."
{:added "1.0"
:static true
:inline (fn x y `(. clojure.lang.Numbers (remainder ~x ~y)))}
num div
(. clojure.lang.Numbers (remainder num div)))

**運行時可用的名字**
與Clojure不同，其他語言的編譯器會將程序中的名字編譯掉使得它們在運行時不可用。而在Clojure中，可以用普通的數據結構對其運行時進行觀察：
; 定義一個var
(def a 42)
; => #'user/a
; 以映射（map）的形式獲取在`user`名字空間中捕獲的（interned）所有var
(ns-publics 'user)
; => {a #'user/a}
; 用`#'`（讀取器宏）及其關聯的、名字空間限定的符號`user/a`引用該var
#'user/a
; => #'user/a
; 解引用該var（獲取其值）
(deref #'user/a)
; => 42
; 定義（並附加文檔字符串）一個函數以返回n除10之餘
(defn foo "返回`(rem n 10)`" n (rem n 10))
; => #'user/foo
; 獲取var `#'user/foo`的元數據
(meta #'user/foo)
; =>
{:arglists (n),
:doc "返回`(rem n 10)`",
:line 1,
:column 1,
:file "user.clj",
:name foo,
:ns #namespaceuser}

**代碼即數據（同像性）**
與其他Lisp類似，Clojure也具有同像性（又稱代碼即數據）。從下面的示例中可以看到，用Clojure編寫代碼從而修改代碼本身是非常容易的：
; 調用一個函數 （代碼）
(+ 1 1)
; => 2
; 引用該函數調用
;（將代碼轉換成數據，此處為含一組符號的列表）
(quote (+ 1 1))
; => (+ 1 1)
; 獲取該列表上的首個元素
; （視代碼為數據並對其進行操作）
(first (quote (+ 1 1)))
; => +
; 獲取該列表上的最後一個元素
; （視代碼為數據並對其進行操作）
(last (quote (+ 1 1)))
; => 1
; 替換原列表上的符號從而獲取一個新列表
; （視代碼為數據並對其進行操作）
(map (fn form
(case form
1 'one
+ 'plus))
(quote (+ 1 1)))
; => (plus one one)

**富有表現力的數據變換操作符**
穿梭宏（threading macro，如，->、->>等）可以在語法上表達一個數據集在一系列變換間穿梭的抽象：
(->> (range 10)
(map inc)
(filter even?))
; => (2 4 6 8 10)
利用變換歸納器也可以更有效地實現該過程：
(sequence (comp (map inc)
(filter even?))
(range 10))
; => (2 4 6 8 10)

**標識及其狀態的線程安全管理**
線程安全的唯一序列號生成器（然而，和許多其他Lisp方言一樣，Clojure內部使用其內置的gensym函數）：
(def i (atom 0))
(defn generate-unique-id
"每次調用會返回一個唯一數字ID。"

(swap! i inc))

**宏**
java.io.Writer的一個匿名子類（不寫任何內容）和一個宏（使用該類來靜音其中打印的所有內容）:
(def bit-bucket-writer
(proxy java.io.Writer 
(write buf nil)
(close     nil)
(flush     nil)))
(defmacro noprint
"在對給定的`forms`求值的同時靜音所有向`*out*`打印的內容。"
& forms
`(binding *out* bit-bucket-writer
~@forms))
(noprint
(println "Hello, nobody!"))
; => nil

**Java互操作**
作為其主要設計目標之一，Clojure從一開始就將其宿主平臺視為其不可分割的一部分。Clojure與Java之間出色的互操作即得益於此：
; 調用一個實例方法
(.toUpperCase "apple")
; => "APPLE"
; 調用一個靜態方法
(System/getProperty "java.vm.version")
; => "12+33"
; 創建`java.util.HashMap`的一個實例
; 並加入一些鍵值對（key-value pairs）
(doto (java.util.HashMap.)
(.put "apple" 1)
(.put "banana" 2))
; => {"banana" 2, "apple" 1}
; 創建`java.util.ArrayList`的一個實例
; 並用`clojure.core/map`遞增（increment）其元素
(def al (doto (java.util.ArrayList.)
(.add 1)
(.add 2)
(.add 3)))
(map inc al)
; => (2 3 4)
; 利用Java Swing顯示一個消息對話框
(javax.swing.JOptionPane/showMessageDialog
nil
"Hello, World!")
; => nil

**軟件事務內存**
10個線程操縱一個共享數據結構，該結構由100個向量組成，而每個向量包含10個（最初是連續的）唯一數字。每個線程隨後在兩個隨機向量中重複選擇兩個隨機位置並交換它們。通過使用Clojure的軟件事務內存系統，對向量的所有更改都發生在事務中：
(defn run
nvecs nitems nthreads niters
(let vec-refs
(->> (* nvecs nitems)
(range)
(into  (comp (partition-all nitems)
(map vec)
(map ref))))
swap
#(let v1 (rand-int nvecs)
v2 (rand-int nvecs)
i1 (rand-int nitems)
i2 (rand-int nitems)
(dosync
(let tmp (nth @(vec-refs v1) i1)
(alter (vec-refs v1) assoc i1 (nth @(vec-refs v2) i2))
(alter (vec-refs v2) assoc i2 tmp))))
report
#(->> vec-refs
(into  (comp (map deref)
(map (fn v (prn v) v))
cat
(distinct)))
(count)
(println "Distinct:"))
(report)
(->> #(dotimes _ niters (swap))
(repeat nthreads)
(apply pcalls)
(dorun))
(report)))
(run 100 10 10 100000)
; =>
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
...
990 991 992 993 994 995 996 997 998 999
Distinct: 1000
382 318 466 963 619 22 21 273 45 596
808 639 804 471 394 904 952 75 289 778
...
484 216 622 139 651 592 379 228 242 355
Distinct: 1000
nil

**參考文獻**

**延伸閱讀**

**外部鏈接==