本页使用了标题或全文手工转换

Clojure

维基百科,自由的百科全书
跳到导航 跳到搜索
Clojure
Clojure logo.svg
编程范型 函数式
設計者 里奇·希基(Rich Hickey)
发行时间 2007年,​11年前​(2007
穩定版本
1.9[1]
( 2017年12月8日,​11個月前​(2017-12-08
型態系統 动态类型强类型
系统平台 Java, JavaScript, .NET
許可證 Eclipse公共许可证
文件扩展名 .clj, .cljs, .cljc, .edn
網站 clojure.org
啟發語言
C#, C++,[2] Common Lisp, Erlang, Haskell, Java, Mathematica,[3] ML, Prolog, Racket,[4] Ruby,[5] Scheme
影響語言
Elixir, Hy, Pixie, Rhine

Clojure/ˈklʒər/, 发音类似"closure")[6]Lisp编程语言Java平台上的现代、动态函数式方言。[7][8] 与其他Lisp一样,Clojure视代码为数据且拥有一套Lisp系统。[9] 其开发过程目前由社区驱动,[10] 其作者里奇·希基(Rich Hickey)则以终身仁慈独裁者(BDFL)的身份进行监督。[11]

Clojure鼓励不可变性(immutability)与持久数据结构(persistent data structures)。尽管其类型系统是完全动态的,人们近期也开始探索其基于渐进类型化(gradual typing)的实现。[12] Clojure鼓励程序员显式地管理状态(state)与标识(identity)。[13] 对利用不可变值(immutable values)及显式时间进展构造(explicit progression-of-time constructs)进行编程的专注旨在促进更加健壮的(尤其是多线程)程序的开发。[14][15]

业内使用Clojure的公司有Funding Circle、[16] 沃尔玛[17] Puppet、[18] 苹果公司[19][20]及其他大型软件公司。[21] 对Clojure的商业支持由Cognitect公司提供。[22] 每年都会在全球范围内举办年度Clojure会议,其中最着名的是Clojure/conj(美国东海岸)、[23] Clojure/West(美国西海岸)[24]和EuroClojure(欧洲)。[25]

历史与开发过程[编辑]

里奇·希基(Rich Hickey)2009年6月3日于旧金山

里奇·希基(Rich Hickey)是Clojure语言的创造者。[7] 在Clojure之前,他开发过类似但基于.NET平台的项目——dotLisp。[26] 在那之前,他还有过三次旨在Lisp与Java之间提供互操作(interoperability)的尝试:“Common Lisp的Java外语接口”(Java foreign language interface for Common Lisp (jfli))、[27] “Lisp的外语对象接口”(Foreign Object Interface for Lisp (FOIL))[28] 以及“Lisp友好的Java Servlet接口”(Lisp-friendly interface to Java Servlets (Lisplets))。[29]

在公开发布之前,里奇·希基在Clojure的工作上花了大约两年半的时间。期间,在没有外部资金的情况下,他将其大部分时间都专门投入到了Clojure的工作上。在这段时间结束时,里奇·希基以电子邮件的形式向他在Common Lisp社区里的一些朋友宣布了该语言。

Clojure的开发过程由社区驱动[10]并且在“Clojure社区”网站上进行管理。[30] 该网站上有其规划文档及一款用于提交错误(bug)报告的问题跟踪器(issue tracker)。一般的开发讨论是在“Clojure Dev谷歌网上论坛”(Clojure Dev Google Group)上进行的。[31] 任何人都可以提交错误报告和想法,而贡献补丁(patches)则需要先签署“Clojure贡献者协议”(Clojure Contributor Agreement)。[32] JIRA错误报告(JIRA ticket)由一个筛选者(screener)团队进行处理并最终由里奇·希基批准更改。[33]

设计理念[编辑]

里奇·希基开发Clojure的原因是因为他想要一款适合函数式编程的现代Lisp。该语言既需要与已建立的Java平台共生又需要有适合并发性的设计。[14][15][34]

Clojure对待变化(change)的方式以标识(identity)的概念为特征。[35] 标识是指随着时间的推移而产生的一系列状态(state)。而状态则是指标识在某一特定时间点上的值(value)。需要强调的是,这里的值是不可变的(immutable)。由此引申,由于状态是不可变的值,任意数量的工作单位(workers)都可以在其上以并行(parallel)的方式实施操作。因此,并发性(concurrency)就成为一道管理状态间变化的问题[注意,这里的“变化”是指从一个状态到另外一个状态的跃迁(transition)而不是状态本身的变化(mutation)。]为此,Clojure提供了几个可变的(mutable)引用类型(reference type)。每个引用类型都有其明确定义的语义用于控制状态之间的跃迁。[13][35]

功能特点[编辑]

版本 发布日期 增加的主要功能
2007年10月16日[36] 首次公开发布
1.0 2009年5月4日[37] 首个稳定版[38]
1.1 2009年12月31日[37] 将来(future)[39]
1.2 2010年8月19日[37] 协议(protocol)[40]
1.3 2011年9月23日[37] 增强对原始类型(primitive type)的支持[41]
1.4 2012年4月15日[37] 读取器字面量(reader literal)
1.5 2013年3月1日[37] 归纳器(reducer)
1.6 2014年3月25[42] Java API、经过改善的哈希算法
1.7 2015年6月30日[43] 变换归纳器(transducer)、读取器条件表达式(reader conditionals)
1.8 2016年1月19日[44] 附加的字符串函数、直接连接(direct linking)、套接字服务器(socket server)
當前版本: 1.9 2017年12月8日[45] 集成spec、命令行工具
格式:
舊版本
舊版本,仍被支持
当前版本
最新的预览版
未来版本

Clojure执行于Java平台之上,因此,与Java紧密集成并完全支持从Clojure调用Java代码。[46] 与此同时,也可以从Java调用Clojure代码。[47] Leiningen[48]是社区中普遍使用的项目自动化工具。Leiningen为Maven集成提供支持,处理项目包管理和依赖项。Leiningen的配置使用的则是Clojure语法。[48]

与其他大多数Lisp一样,Clojure的语法建立在S-表达式之上。S-表达式在被编译之前先由读取器(reader)解析为数据结构。[49] 除了列表(list)之外,Clojure的读取器还支持映射(map)、集合(set)及向量(vector)等的字面量(literal)语法。这些字面量随后会被直接编译成上述数据结构。[49] Clojure是Lisp-1且有一套与其它Lisp不兼容的数据结构,因此,Clojure不支持与Lisp的其它方言之间的代码级兼容性。[49][50]

作为一门Lisp方言,函数在Clojure中是一等公民。此外,Clojure还支持读取﹣求值﹣输出循环(REPL)以及一套宏系统。[51] Clojure的Lisp宏系统与Common Lisp的系统极为相似。唯一不同的是,Clojure的重音符[称为“语法引用”(syntax quote)]用名字空间(namespace)来限定符号(symbol)。这有助于防止意外的名字捕获(unintended name capture),因为Clojure禁止绑定(binding)到用名字空间限定的名字(namespace-qualified name)上。如果需要强制捕获宏扩展(capturing macro expansion,)那么就需要显示地完成该过程。Clojure不支持用户定义的读取器宏(reader macro,)但Clojure的读取器支持更具约束力的语法扩展(syntactic expansion)形式。[52] Clojure支持多重方法(multimethods。)[53] 对于类似接口的抽象,Clojure提供基于协议(protocol)[54]的多态性(polymorphism)以及基于记录(record)[55]的数据类型系统。 Clojure通过这些设计来提供高性能且动态的多态性以避免所谓的“表达式问题”("expression problem"。)

Clojure支持惰性序列,并鼓励不可变性(immutability)与持久数据结构(persistent data structure。)Clojure作为一门函数式编程语言将重点放在递归高阶函数上而不是基于副作用循环流程上。Clojure不支持自动尾调用优化,因为JVM还不支持该项优化,[56][57][58]但是,可以用recur关键字显式地进行该项优化。[59] 对于并行并发计算,Clojure提供软件事务内存[60] 响应式代理系统[61]及基于通道(channel)的并发编程。[62]

Clojure 1.7引入了读取器条件表达式(reader conditional)从而允许在同一名字空间中嵌入Clojure与ClojureScript代码。[43][63] 变换归纳器(transducer)的加入则提供了另一种组合变换的方法。变换归纳器可以使高阶函数(如,mapfold)更加抽象从而使之独立于其输入数据源。传统地说,这些函数一般被应用于序列(sequence)上,而变换归纳器允许这些函数被应用于通道(channel)上并让用户定义她们自己的变换归纳(transduction)模型。[64][65][66]

平台与人气[编辑]

Clojure的主要平台是Java[8][46]但也存在其他目标平台上的实现。其中,最值得关注的是ClojureScript[67](可被编译成ECMAScript 3[68])和ClojureCLR[69].NET平台上的完整移植版,可与其生态系统互操作。)2013年对1,060名受访者进行的Clojure社区调查[70]发现,47%的受访者在使用Clojure的同时也使用ClojureScript。2014年,这一数字增长到了55%,[71]而到了2015年,则达到了66%(根据2,445名受访者)。[72] 人气较高的ClojureScript项目包括React实现,如Reagent[73]和Om。[74][75]

Clojure也一度被用于创意计算,包括视觉艺术、音乐、游戏和诗歌。[76]

越来越多的非官方和/或实验性的其他平台实现也验证了该语言的人气:

  • las3r,[77] 执行于ActionScript虚拟机(Adobe Flash Player平台)的Clojure子集
  • clojure-py,[78] Clojure的纯Python实现
  • rouge,[79] Clojure基于YARV的Ruby实现
  • CljPerl,[80] Clojure的Perl实现
  • Pixie, 受Clojure启发并用RPython实现的Lisp方言
  • Ferret,[81] 可被编译成运行于微控制器的自包含(self-contained)C++11
  • Joker,[82]Go实现的解释器(interpreter)和linter
  • clojerl,[83] 基于Erlang虚拟机的Clojure

示例[编辑]

“Hello, world!”程序:

;; 打印到标准控制台
(println "Hello, world!")

;; 使用Java Swing库
(javax.swing.JOptionPane/showMessageDialog
  nil
  "Hello, world!")

Unicode支持(打印"Hello, 世界!"):

;; 打印到标准控制台
(println "Hello, 世界!")

;; 使用Java Swing库
(javax.swing.JOptionPane/showMessageDialog
  nil
  "Hello, 世界!")

定义一个函数:

(defn square [x]
  (* x x))

与其他Lisp一样,Clojure的标志性特征之一是基于REPL的交互式开发(=>表示输出):

;; 定义一个var
(def a 42)
=> #'user/a

;; 调用名为`+`的函数
(+ a 8)
=> 50

;; 调用名为`even?`的函数
(even? a)
=> true

;; 定义一个函数以返回n除10之余
(defn foo "返回`(rem n 10)`" [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也具有同像性[又被称为“视代码为数据”(code as data)]。从下面的示例中可以看到,用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)

利用变换归纳器(transducer)也可以更有效地实现该过程:

(into []
      (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

10个线程操纵一个共享数据结构,该结构由100个向量组成,而每个向量包含10个(最初是连续的)唯一数字。每个线程随后在两个随机向量中重复选择两个随机位置并交换它们。通过使用Clojure的软件事务内存系统,对向量的所有更改都发生在事务中:

(defn run
  [nvecs nitems nthreads niters]
  (let [vec-refs
        (->> (* nvecs nitems)
          (range)
          (partition nitems)
          (map (comp ref vec))
          (vec))

        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
        #(let [derefed (map deref vec-refs)]
          (prn derefed)
          (println "Distinct:" (->> derefed
                                 (apply concat)
                                 (distinct)
                                 (count))))]
    (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

编辑器/集成开发环境(IDE)[编辑]

参考文献[编辑]

  1. ^ Index of /maven2/org/clojure/clojure/1.9.0/. repo1.maven.org. 2017-12-08 [2017-12-08]. 
  2. ^ Fogus, Michael. Rich Hickey Q&A. CodeQuarterly.com. [2017-01-11]. 
  3. ^ Hickey, Rich. Clojure Bookshelf. Listmania!. Amazon.com. [2017-10-03]. 
  4. ^ Bonnaire-Sergeant, Ambrose. A Practical Optional Type System for Clojure (Thesis). The University of Western Australia. 2012. 
  5. ^ Clojure Programming (PDF). [2013-04-30]. 
  6. ^ meaning and pronunciation of Clojure. Rich Hickey. [2012-04-20]. 
  7. ^ 7.0 7.1 Krill, Paul. Clojure inventor Hickey now aims for Android. www.infoworld.com. [2018-11-08]. 
  8. ^ 8.0 8.1 Clojure – 首页. Clojure.org. [2018-10-31]. 
  9. ^ Clojure – Lisp. Clojure.org. [2015-09-15]. 
  10. ^ 10.0 10.1 Contributing FAQ - Clojure Community - Clojure Development. dev.clojure.org. [2015-09-15]. 
  11. ^ Clojure - funding. clojure.org. [2015-09-15]. 
  12. ^ clojure/core.typed. GitHub. [2015-09-15]. 
  13. ^ 13.0 13.1 Clojure – state. Clojure.org. [2015-09-15]. 
  14. ^ 14.0 14.1 Hickey, Rich. Rationale. Clojure.org. [2008-10-17]. 
  15. ^ 15.0 15.1 Charles. Expert to Expert: Rich Hickey and Brian Beckman – Inside Clojure; Going Deep; Channel 9. Channel9.msdn.com. 2009-10-06 [2012-06-28]. 
  16. ^ JUXT Blog: Clojure in London: Funding Circle. juxt.pro. [2017-02-01]. 
  17. ^ Walmart Runs Clojure at Scale. Cognitect.com. [2015-09-15]. 
  18. ^ A New Era of Application Services at Puppet Labs. Puppet Labs. [2015-09-15]. 
  19. ^ Roman Liutikov at Twitter. Twitter. [2018-10-27]. 
  20. ^ Jobs at Apple. Apple Inc. [2018-10-27]. 
  21. ^ Clojure - Success Stories. Clojure.org. [2018-10-27]. 
  22. ^ Clojure Programming Language: Cognitect, Clojure Consulting, Clojure Support, Functional Programming, JVM. Cognitect.com. [2015-09-15]. 
  23. ^ Clojure/conj
  24. ^ Clojure/West
  25. ^ EuroClojure
  26. ^ [ANN] dotLisp – a Lisp dialect for .NET. Google Groups. 
  27. ^ jfli, a Java foreign language interface for Common Lisp. 
  28. ^ FOIL – a Foreign Object Interface for Lisp. 
  29. ^ Lisplets – a Lisp-friendly interface to Java Servlets. 
  30. ^ Clojure Community website
  31. ^ Clojure Dev Google Group
  32. ^ Contributing FAQ – Clojure Community – Clojure Development. dev.clojure.org. [2015-09-16]. 
  33. ^ JIRA workflow – Clojure Community – Clojure Development. dev.clojure.org. [2015-09-16]. 
  34. ^ Economy Size Geek – Interview with Rich Hickey, Creator of Clojure. Linux Journal. [2015-09-15]. 
  35. ^ 35.0 35.1 Hickey, Rich. On State and Identity. Clojure.org. [2010-03-01]. 
  36. ^ Clojure: Clojure is Two!. Clojure Blog. [2015-09-16]. 
  37. ^ 37.0 37.1 37.2 37.3 37.4 37.5 Fingerhut, Andy. Clojure version history. jafingerhut.github.io. [2015-09-16]. 
  38. ^ Clojure: Clojure 1.0. Clojure Blog. [2015-09-16]. 
  39. ^ Clojure: Clojure 1.1 Release. clojure.blogspot.com. [2015-09-16]. 
  40. ^ Clojure – protocols. Clojure.org. [2015-09-16]. 
  41. ^ Clojure/clojure. GitHub. [2015-09-16]. 
  42. ^ Google Groups. groups.google.com. [2015-09-16]. 
  43. ^ 43.0 43.1 Clojure 1.9. Cognitect. [2017-12-08]. 
  44. ^ Google Groups. groups.google.com. [2016-01-25]. 
  45. ^ Google Groups. groups.google.com. [2017-12-08]. 
  46. ^ 46.0 46.1 Clojure – jvm_hosted. Clojure.org. [2015-09-15]. 
  47. ^ Clojure – java_interop. Clojure.org. [2015-09-15]. 
  48. ^ 48.0 48.1 Hagelberg, Phil; contributors. Leiningen. leiningen.org. [2015-09-15]. 
  49. ^ 49.0 49.1 49.2 Clojure – reader. Clojure.org. [2015-09-15]. 
  50. ^ Clojure – Lisps. Clojure.org. [2015-09-15]. 
  51. ^ Clojure – macros. Clojure.org. [2015-09-15]. 
  52. ^ Hickey, Rich. edn. GitHub. [2014-05-24]. 
  53. ^ Clojure – multimethods. Clojure.org. [2015-09-15]. 
  54. ^ Clojure – protocols. Clojure.org. [2015-09-15]. 
  55. ^ Clojure – datatypes. Clojure.org. [2015-09-15]. 
  56. ^ Goetz, Brian. Brian Goetz – Stewardship: the Sobering Parts. YouTube: ClojureTV. [2015-09-15]. 
  57. ^ Rose, John. tail calls in the VM. blogs.oracle.com. [2018-11-03]. 
  58. ^ Rose, John. Some languages need to be able to perform tail calls. JDK Bug System. [2018-11-03]. 
  59. ^ Clojure – special_forms. Clojure.org. [2015-09-15]. 
  60. ^ Clojure – Refs. Clojure.org. [2015-09-15]. 
  61. ^ Clojure – Agents. Clojure.org. [2016-07-04]. 
  62. ^ Clojure: Clojure core.async Channels. Clojure.com. [2015-09-15]. 
  63. ^ Clojure – reader. Clojure.org. [2015-09-15]. 
  64. ^ "Transducers" by Rich Hickey. https://www.youtube.com/watch?v=6mTbuzafcII. Retrieved on 2015-09-15.
  65. ^ Transducers are Coming. [2015-09-15]. 
  66. ^ Rich Hickey – Inside Transducers. YouTube: Cognitect Inc. 2014-11-20 [2015-09-15]. 
  67. ^ Clojure/Clojurescript. GitHub. [2015-09-15]. 
  68. ^ ClojureScript – FAQ (for JavaScript developers). clojurescript.org. [2018-02-04]. 
  69. ^ clojure/clojure-clr. GitHub. [2012-06-28]. 
  70. ^ Emerick, Chas. Results of the 2013 State of Clojure & ClojureScript survey. Cemerick. [2015-09-17]. 
  71. ^ State of Clojure 2014 Survey Results. Cognitect Blog. [2015-09-17]. 
  72. ^ State of Clojure 2015 Survey Results. Cognitect Blog. [2016-09-08]. 
  73. ^ Reagent. GitHub. 
  74. ^ Om. GitHub. 
  75. ^ Om: Enhancing Facebook's React with Immutability. InfoQ. [2015-09-17]. 
  76. ^ Meier, Carin. Creative computing with Clojure. O'Reilly Radar. [2015-09-17]. 
  77. ^ aemoncannon. Home: aemoncannon/las3r Wiki. GitHub. 2010-12-30 [2012-06-28]. 
  78. ^ drewr/clojure-py. GitHub. [2017-01-10]. 
  79. ^ rouge-lang/rouge. GitHub. [2015-12-19]. 
  80. ^ A Lisp on Perl. MetaCPAN. [2014-05-25]. 
  81. ^ Ferret. GitHub. 
  82. ^ Joker. GitHub. 
  83. ^ clojerl. GitHub. 

延伸阅读[编辑]

外部链接[编辑]