源代碼下載:?learnclojure-cn.clj
Clojure是運(yùn)行在JVM上的Lisp家族中的一員。她比Common Lisp更強(qiáng)調(diào)純函數(shù)式編程,且自發(fā)布時(shí)便包含了一組工具來處理狀態(tài)。
這種組合讓她能十分簡單且自動(dòng)地處理并發(fā)問題。
(你需要使用Clojure 1.2或更新的發(fā)行版)
; 注釋以分號(hào)開始。
; Clojure代碼由一個(gè)個(gè)form組成, 即寫在小括號(hào)里的由空格分開的一組語句。
; Clojure解釋器會(huì)把第一個(gè)元素當(dāng)做一個(gè)函數(shù)或者宏來調(diào)用,其余的被認(rèn)為是參數(shù)。
; Clojure代碼的第一條語句一般是用ns來指定當(dāng)前的命名空間。
(ns learnclojure)
; 更基本的例子:
; str會(huì)使用所有參數(shù)來創(chuàng)建一個(gè)字符串
(str "Hello" " " "World") ; => "Hello World"
; 數(shù)學(xué)計(jì)算比較直觀
(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 1 2) ; => 2
(/ 2 1) ; => 2
; 等號(hào)是 =
(= 1 1) ; => true
(= 2 1) ; => false
; 邏輯非
(not true) ; => false
; 嵌套的form工作起來應(yīng)該和你預(yù)想的一樣
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
; 類型
;;;;;;;;;;;;;
; Clojure使用Java的Object來描述布爾值、字符串和數(shù)字
; 用函數(shù) `class` 來查看具體的類型
(class 1) ; 整形默認(rèn)是java.lang.Long類型
(class 1.); 浮點(diǎn)默認(rèn)是java.lang.Double類型的
(class ""); String是java.lang.String類型的,要用雙引號(hào)引起來
(class false) ; 布爾值是java.lang.Boolean類型的
(class nil); "null"被稱作nil
; 如果你想創(chuàng)建一組數(shù)據(jù)字面量,用單引號(hào)(')來阻止form被解析和求值
'(+ 1 2) ; => (+ 1 2)
; (單引號(hào)是quote的簡寫形式,故上式等價(jià)于(quote (+ 1 2)))
; 可以對(duì)一個(gè)引用列表求值
(eval '(+ 1 2)) ; => 3
; 集合(Collection)和序列
;;;;;;;;;;;;;;;;;;;
; List的底層實(shí)現(xiàn)是鏈表,Vector的底層實(shí)現(xiàn)是數(shù)組
; 二者也都是java類
(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList
; list本可以寫成(1 2 3), 但必須用引用來避免被解釋器當(dāng)做函數(shù)來求值。
; (list 1 2 3)等價(jià)于'(1 2 3)
; 集合其實(shí)就是一組數(shù)據(jù)
; List和Vector都是集合:
(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true
; 序列 (seqs) 是數(shù)據(jù)列表的抽象描述
; 只有列表才可稱作序列。
(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false
; 序列被訪問時(shí)只需要提供一個(gè)值,所以序列可以被懶加載——也就意味著可以定義一個(gè)無限序列:
(range 4) ; => (0 1 2 3)
(range) ; => (0 1 2 3 4 ...) (無限序列)
(take 4 (range)) ; (0 1 2 3)
; cons用以向列表或向量的起始位置添加元素
(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)
; conj將以最高效的方式向集合中添加元素。
; 對(duì)于列表,數(shù)據(jù)會(huì)在起始位置插入,而對(duì)于向量,則在末尾位置插入。
(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)
; 用concat來合并列表或向量
(concat [1 2] '(3 4)) ; => (1 2 3 4)
; 用filter來過濾集合中的元素,用map來根據(jù)指定的函數(shù)來映射得到一個(gè)新的集合
(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)
; recuce使用函數(shù)來規(guī)約集合
(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10
; reduce還能指定一個(gè)初始參數(shù)
(reduce conj [] '(3 2 1))
; = (conj (conj (conj [] 3) 2) 1)
; => [3 2 1]
; 函數(shù)
;;;;;;;;;;;;;;;;;;;;;
; 用fn來創(chuàng)建函數(shù)。函數(shù)的返回值是最后一個(gè)表達(dá)式的值
(fn [] "Hello World") ; => fn
; (你需要再嵌套一組小括號(hào)來調(diào)用它)
((fn [] "Hello World")) ; => "Hello World"
; 你可以用def來創(chuàng)建一個(gè)變量(var)
(def x 1)
x ; => 1
; 將函數(shù)定義為一個(gè)變量(var)
(def hello-world (fn [] "Hello World"))
(hello-world) ; => "Hello World"
; 你可用defn來簡化函數(shù)的定義
(defn hello-world [] "Hello World")
; 中括號(hào)內(nèi)的內(nèi)容是函數(shù)的參數(shù)。
(defn hello [name]
(str "Hello " name))
(hello "Steve") ; => "Hello Steve"
; 你還可以用這種簡寫的方式來創(chuàng)建函數(shù):
(def hello2 #(str "Hello " %1))
(hello2 "Fanny") ; => "Hello Fanny"
; 函數(shù)也可以有多個(gè)參數(shù)列表。
(defn hello3
([] "Hello World")
([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"
; 可以定義變參函數(shù),即把&后面的參數(shù)全部放入一個(gè)序列
(defn count-args [& args]
(str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
; 可以混用定參和變參(用&來界定)
(defn hello-count [name & args]
(str "Hello " name ", you passed " (count args) " extra args"))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"
; 哈希表
;;;;;;;;;;
; 基于hash的map和基于數(shù)組的map(即arraymap)實(shí)現(xiàn)了相同的接口,hashmap查詢起來比較快,
; 但不保證元素的順序。
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap
; arraymap在足夠大的時(shí)候,大多數(shù)操作會(huì)將其自動(dòng)轉(zhuǎn)換成hashmap,
; 所以不用擔(dān)心(對(duì)大的arraymap的查詢性能)。
; map支持很多類型的key,但推薦使用keyword類型
; keyword類型和字符串類似,但做了一些優(yōu)化。
(class :a) ; => clojure.lang.Keyword
(def stringmap {"a" 1, "b" 2, "c" 3})
stringmap ; => {"a" 1, "b" 2, "c" 3}
(def keymap {:a 1, :b 2, :c 3})
keymap ; => {:a 1, :c 3, :b 2}
; 順便說一下,map里的逗號(hào)是可有可無的,作用只是提高map的可讀性。
; 從map中查找元素就像把map名作為函數(shù)調(diào)用一樣。
(stringmap "a") ; => 1
(keymap :a) ; => 1
; 可以把keyword寫在前面來從map中查找元素。
(:b keymap) ; => 2
; 但不要試圖用字符串類型的key來這么做。
;("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn
; 查找不存在的key會(huì)返回nil。
(stringmap "d") ; => nil
; 用assoc函數(shù)來向hashmap里添加元素
(def newkeymap (assoc keymap :d 4))
newkeymap ; => {:a 1, :b 2, :c 3, :d 4}
; 但是要記住的是clojure的數(shù)據(jù)類型是不可變的!
keymap ; => {:a 1, :b 2, :c 3}
; 用dissoc來移除元素
(dissoc keymap :a :b) ; => {:c 3}
; 集合(Set)
;;;;;;
(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}
; 用conj新增元素
(conj #{1 2 3} 4) ; => #{1 2 3 4}
; 用disj移除元素
(disj #{1 2 3} 1) ; => #{2 3}
; 把集合當(dāng)做函數(shù)調(diào)用來檢查元素是否存在:
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil
; 在clojure.sets模塊下有很多相關(guān)函數(shù)。
; 常用的form
;;;;;;;;;;;;;;;;;
; clojure里的邏輯控制結(jié)構(gòu)都是用宏(macro)實(shí)現(xiàn)的,這在語法上看起來沒什么不同。
(if false "a" "b") ; => "b"
(if false "a") ; => nil
; 用let來創(chuàng)建臨時(shí)的綁定變量。
(let [a 1 b 2]
(> a b)) ; => false
; 用do將多個(gè)語句組合在一起依次執(zhí)行
(do
(print "Hello")
"World") ; => "World" (prints "Hello")
; 函數(shù)定義里有一個(gè)隱式的do
(defn print-and-say-hello [name]
(print "Saying hello to " name)
(str "Hello " name))
(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")
; let也是如此
(let [name "Urkel"]
(print "Saying hello to " name)
(str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")
; 模塊
;;;;;;;;;;;;;;;
; 用use來導(dǎo)入模塊里的所有函數(shù)
(use 'clojure.set)
; 然后就可以使用set相關(guān)的函數(shù)了
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
(difference #{1 2 3} #{2 3 4}) ; => #{1}
; 你也可以從一個(gè)模塊里導(dǎo)入一部分函數(shù)。
(use '[clojure.set :only [intersection]])
; 用require來導(dǎo)入一個(gè)模塊
(require 'clojure.string)
; 用/來調(diào)用模塊里的函數(shù)
; 下面是從模塊`clojure.string`里調(diào)用`blank?`函數(shù)。
(clojure.string/blank? "") ; => true
; 在`import`里你可以給模塊名指定一個(gè)較短的別名。
(require '[clojure.string :as str])
(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
; (#""用來表示一個(gè)正則表達(dá)式)
; 你可以在一個(gè)namespace定義里用:require的方式來require(或use,但最好不要用)模塊。
; 這樣的話你無需引用模塊列表。
(ns test
(:require
[clojure.string :as str]
[clojure.set :as set]))
; Java
;;;;;;;;;;;;;;;;;
; Java有大量的優(yōu)秀的庫,你肯定想學(xué)會(huì)如何用clojure來使用這些Java庫。
; 用import來導(dǎo)入java類
(import java.util.Date)
; 也可以在ns定義里導(dǎo)入
(ns test
(:import java.util.Date
java.util.Calendar))
; 用類名末尾加`.`的方式來new一個(gè)Java對(duì)象
(Date.) ; <a date object>
; 用`.`操作符來調(diào)用方法,或者用`.method`的簡化方式。
(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.)) ; 和上例一樣。
; 用`/`調(diào)用靜態(tài)方法
(System/currentTimeMillis) ; <a timestamp> (system is always present)
; 用`doto`來更方便的使用(可變)類。
(import java.util.Calendar)
(doto (Calendar/getInstance)
(.set 2000 1 1 0 0 0)
.getTime) ; => A Date. set to 2000-01-01 00:00:00
; STM
;;;;;;;;;;;;;;;;;
; 軟件內(nèi)存事務(wù)(Software Transactional Memory)被clojure用來處理持久化的狀態(tài)。
; clojure里內(nèi)置了一些結(jié)構(gòu)來使用STM。
; atom是最簡單的。給它傳一個(gè)初始值
(def my-atom (atom {}))
; 用`swap!`更新atom。
; `swap!`會(huì)以atom的當(dāng)前值為第一個(gè)參數(shù)來調(diào)用一個(gè)指定的函數(shù),
; `swap`其余的參數(shù)作為該函數(shù)的第二個(gè)參數(shù)。
(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)
(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)
; 用`@`讀取atom的值
my-atom ;=> Atom<#...> (返回Atom對(duì)象)
@my-atom ; => {:a 1 :b 2}
; 下例是一個(gè)使用atom實(shí)現(xiàn)的簡單計(jì)數(shù)器
(def counter (atom 0))
(defn inc-counter []
(swap! counter inc))
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
@counter ; => 5
; 其他STM相關(guān)的結(jié)構(gòu)是ref和agent.
; Refs: http://clojure.org/refs
; Agents: http://clojure.org/agents
本文肯定不足以講述關(guān)于clojure的一切,但是希望足以讓你邁出第一步。
Clojure.org官網(wǎng)有很多文章:?http://clojure.org/
Clojuredocs.org有大多數(shù)核心函數(shù)的文檔,還帶了示例哦:http://clojuredocs.org/quickref/Clojure%20Core
4Clojure是個(gè)很贊的用來練習(xí)clojure/FP技能的地方:?http://www.4clojure.com/
Clojure-doc.org (你沒看錯(cuò))有很多入門級(jí)的文章:?http://clojure-doc.org/
更多建議: