Clojure

編程語言 来自维基百科,自由的百科全书

Clojure

Clojure/ˈklʒər/[16][17]Lisp程式語言Java平台上的現代、動態函數式方言。[18][19] 與其他Lisp一樣,Clojure視代碼為數據且擁有一套Lisp巨集系統。[20]Clojure的開發過程目前由社區驅動,[21]其作者里奇·希基則以終身仁慈獨裁者的身份監督。[22]

快速預覽 編程範型, 設計者 ...
Clojure
Thumb
編程範型多重程式設計範式:
設計者里奇·希基
釋出時間2007年,​18年前​(2007
目前版本
  • 1.12.0(2024年9月5日;穩定版本)[8]
編輯維基數據連結
型態系統
系統平台
許可證Eclipse公共許可證
副檔名
  • .clj
  • .cljs
  • .cljc
  • .edn
網站clojure.org
受影響於
影響語言
關閉

Clojure提倡不可變性與持久數據結構並鼓勵程式設計師顯式地管理標識及其狀態。[23]對利用不可變值及顯式時間進展構造(explicit progression-of-time constructs)進行編程的專注旨在促進更加健壯的(尤其是並行)程式的開發。[24][25][16]Clojure的型別系統是完全動態的,但人們近期也開始探索其基於漸進類型化的實現。[26]

對Clojure的商業支援由Cognitect公司提供。[27] 每年都會在全球範圍內舉辦年度Clojure會議,其中最著名的是Clojure/conj[28]

歷史與開發過程

Thumb
里奇·希基,Clojure的創造者

里奇·希基創造了Clojure語言。[18]此前,他開發過類似但基於.NET平台的專案——dotLisp。[29]在那之前,他還嘗試了三次在Lisp與Java之間提供互操作:Common Lisp的Java外語介面[30]、Lisp的外語對象介面[31]以及Lisp友好的Java Servlet介面[32]

在公開發布之前,里奇·希基花了大約兩年半的時間開發Clojure。他在沒有外部資金的情況下,將其大部分時間都專門投入到了Clojure的工作上。開發快要完成,里奇·希基向Common Lisp社區裏的一些朋友發佈電子郵件,宣佈他完成開發了Clojure。

Clojure的開發過程在Clojure JIRA專案網頁由社區驅動並管理。[21][33]該網站可用於提交問題報告。一般的開發討論是在Clojure谷歌網上討論區上進行的。[34] 任何人都可以提交錯誤報告和想法,而貢獻修補程式前則需要先簽署Clojure貢獻者協定。[35] JIRA錯誤報告由一組篩選者處理,最終由里奇·希基批准更改。[36]

設計理念

里奇·希基開發Clojure的原因是因為他想要一款適合函數式程式設計的現代Lisp。該語言既需要與已建立的Java平台共生又需要有適合並行性的設計。[24][25][37][16]

在Clojure中,狀態以標識的概念為特徵。[23]一系列狀態隨着時間的推移而產生,就是標識。由於狀態是不可變的值,任意數量的工作單位都可以在其上並列實施操作,並行性就成為一道管理狀態到狀態的變化的問題。因此,Clojure提供了幾個可變的參照類型。每個參照類型都有其明確定義的語意用於控制狀態之間的躍遷。[23]

語言概述

更多資訊 版本, 發佈日期 ...
版本 發佈日期 主要功能/改進
2007年10月16日[38] 首次公開發布
1.0 2009年5月4日[39] 首個穩定版
1.1 2009年12月31日[40] 將來
1.2 2010年8月19日[41] 協定
1.3 2011年9月23日[42] 增強對原始類型的支援
1.4 2012年4月15日[43] 讀取器字面量
1.5 2013年3月1日[44] 歸納器
1.5.1 2013年3月10日[45] 修復記憶體流失
1.6 2014年3月25[46] Java API、經過改進的雜湊演算法
1.7 2015年6月30日[47] 變換歸納器、讀取器條件表達式
1.8 2016年1月19日[48] 附加的字串函數、直接連接、通訊端伺服器
1.9 2017年12月8日[49] 整合spec、命令列工具
1.10 2018年12月17日[50] 經過改進的錯誤報告、Java相容性
1.10.1 2019年6月6日[51] 解決Java效能回歸問題並改進clojure.main的錯誤報告
1.10.2 2021年1月26日 (2021-01-26)[52] Java互操作性/相容性改進和其他重要語言修訂
1.10.3 2021年3月4日 (2021-03-04)[53] prepl支援讀者條件
1.11.0 2022年3月22日 (2022-03-22)[54] 新的關鍵字參數呼叫語法,新的clojure.math名字空間,名字空間別名不用載入,並向clojure.core增加新的幫助函數
當前版本: 1.11.1 2022年4月5日 (2022-04-05)[15] 在類型clojure.lang.Keywordclojure.lang.ArraySeq的對象的二進制序列化中轉返意外的變更。
格式:
舊版本
舊版本,仍被支援
目前版本
最新的預覽版
未來版本
關閉

Clojure執行於Java平台之上,因此,與Java緊密整合並完全支援從Clojure呼叫Java代碼。[55][16] 與此同時,也可以從Java呼叫Clojure代碼。[56] Leiningen是社區中普遍使用的專案自動化工具。Leiningen為Maven整合提供支援,處理專案包管理和依賴項。Leiningen的組態使用的則是Clojure語法。[57]

與其他大多數Lisp一樣,Clojure的語法建立在S-表達式之上。S-表達式在被編譯之前先由讀取器解析為數據結構。[58][16] 除了列表之外,Clojure的讀取器還支援對映、集合及向量等的字面量語法。這些字面量隨後會被直接編譯成上述數據結構。[58] Clojure是Lisp-1且有一套與其它Lisp不相容的數據結構,因此,Clojure不支援與Lisp的其它方言之間的代碼級相容性。[20]

作為一門Lisp方言,函數在Clojure中是一等公民。此外,Clojure還支援讀取﹣求值﹣輸出循環以及一套巨集系統。[6] Clojure的Lisp巨集系統與Common Lisp的系統極為相似。唯一不同的是,Clojure的重音符(稱為語法參照)用名字空間來限定符號。這有助於防止意外的名字擷取,因為Clojure禁止繫結到用名字空間限定的名字(namespace-qualified name)上。如果需要強制擷取巨集擴充(capturing macro expansion,)那麼就需要顯示地完成該過程。Clojure不支援用戶定義的讀取器巨集(reader macro,)但Clojure的讀取器支援更具約束力的語法擴充形式。[59] Clojure支援多方法(multimethods。)[60] 對於類似介面的抽象,Clojure提供基於協定[61]的多型性以及基於記錄[62]的資料類型系統。 Clojure通過這些設計來提供高效能且動態的多型性以避免所謂的「表達式問題」("expression problem"。)

Clojure支援惰性序列,並鼓勵不可變性與持久數據結構(persistent data structure。)Clojure作為一門函數式程式設計語言將重點放在遞歸高階函數上而不是基於副作用循環流程上。Clojure不支援自動尾呼叫最佳化,因為JVM還不支援該項最佳化,[63][64][65]但是,可以用recur關鍵字顯式地執行該項最佳化。[66] 對於並列並行計算,Clojure提供軟件事務主記憶體[67] 響應式代理系統[1]及基於通道的並行編程。[68]

Clojure 1.7引入了讀取器條件表達式從而允許在同一名字空間中嵌入Clojure與ClojureScript代碼。[47][58] 變換歸納器的加入則提供了另一種組合變換的方法。變換歸納器可以使高階函數(如,mapfold)更加抽象從而使之獨立於其輸入數據源。傳統地說,這些函數一般被應用於序列上,而變換歸納器允許這些函數被應用於通道上並讓用戶定義她們自己的變換歸納模型。[69][70][71]

平台

Clojure的主要平台是Java[19][55]但也存在其他目標平台上的實現。其中,最值得關注的是ClojureScript[72](可被編譯成ECMAScript 3[73])和ClojureCLR[74].NET平台上的完整移植版,可與其生態系統互操作。)2013年對1,060名受訪者進行的Clojure社區調查[75]發現,47%的受訪者在使用Clojure的同時也使用ClojureScript。2014年,這一數字增長到了55%,[76]而到了2015年,則達到了66%(根據2,445名受訪者)。[77] 人氣較高的ClojureScript專案包括React實現,如Reagent[78]、re-frame[79]、Rum[80]及Om[81][82]

人氣

隨着對函數式程式設計的興趣的持續升溫,Clojure也越來越多地受到Java平台上的軟件開發人員的青睞。該語言也一度成為知名軟件開發老將的首選或推薦語言,如Brian Goetz[83][84][85]、Eric Evans[86][87]詹姆斯·高斯林[88]保羅·格雷厄姆[89]及Robert C. Martin(俗稱「鮑勃大叔」)[90][91][92][93]等人。

在由Snyk和Java Magazine合作編寫的「JVM生態系統報告2018」(據稱是「Java開發人員有史以來規模最大的調查」)中,Clojure被評為用於「主要應用程式」的第二大人氣程式語言(僅次於Java)。[94]

業內使用Clojure的公司有蘋果公司[95][96]Atlassian[97]、Funding Circle[98]Netflix[99]、 Puppet [100]沃爾瑪[101]及其他大型軟件公司[102]美國太空總署[103]等政府機構。Clojure也一度被用於創意計算,包括視覺藝術、音樂、遊戲和詩歌。[104]

美國知名軟件諮詢公司ThoughtWorks在為其「技術雷達」[105]評估函數式程式設計語言時表達了他們對Clojure的青睞,稱其為「Lisp在JVM上的簡單及優雅實現」,並在2012年將其狀態提升為「採用」(「ADOPT」)[106]

越來越多的非官方和/或實驗性的其他平台實現也驗證了該語言的人氣:

  • CljPerl[107]:Clojure的Perl實現
  • Clojerl[108]:BEAM(Erlang虛擬機器)上的Clojure
  • clojure-py[109]:Clojure的純Python實現
  • Ferret[110]:可被編譯成執行於微控制器的自包含(self-contained)C++11
  • Joker[111]:用Go實現的直譯器和linter
  • Las3r[112]:執行於ActionScript虛擬機器(Adobe Flash Player平台)的Clojure子集
  • Pixie[113]:受Clojure啟發並用RPython實現的Lisp方言
  • Rouge[114]:Clojure基於YARV的Ruby實現

開發工具

Clojure的開發工具在近幾年得到了顯著的改善。以下是目前最具人氣的整合式開發環境/編輯器及其Clojure外掛程式。[115]這些工具的結合為Clojure編程提供了出色的支援。

更多資訊 整合式開發環境/編輯器, Clojure外掛程式 ...
整合式開發環境/編輯器及其Clojure外掛程式
整合式開發環境/編輯器 Clojure外掛程式
Atom Chlorine[116]
Emacs CIDER[117]
IntelliJ IDEA Clojure-Kit[118]或Cursive[119](提供免費的非商業許可證)
Light Table[120] (不適用)
Vim fireplace.vim[121][122], vim-iced[123]或Conjure(僅限Neovim)[124][125]
Visual Studio Code Calva[126]
關閉

除了社區提供的開發工具之外,官方的命令列介面工具[127]也隨着Clojure 1.9一起發佈並可在GNU/Linux、macOS及Windows上使用。[128]

功能範例

以下範例均可在Clojure REPL中執行(如,使用Clojure命令列介面工具[127]啟動的REPL或在REPL.it[129]上提供的線上REPL。)

簡單性

由於強調簡單性,典型的Clojure程式主要包括函數和簡單的數據結構(即列表,向量,對映和集合):

;; 一个典型的Clojure程序的入口
(defn -main ; 函数名
  [& args] ; 参数向量 (`&`表示可变参数)
  (println "Hello, World!")) ; 函数体

REPL編程

與其他Lisp一樣,Clojure的標誌性特徵之一是基於REPL的互動式編程。[130]在以下範例中,;;表示一行註釋的開始,而;; =>則表示輸出:

;; 定义一个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 #namespace[user]}

代碼即數據(同像性)

與其他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

參考文獻

延伸閱讀

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.