Clojure是JVM上的一個LISP語言變種。它比Common Lisp更強調純函數式編程,但提供了一些STM工具以處理它所引入的狀態問題。
這個組合使並行處理更加簡單,並且通常是自動的。
(你需要Clojure 1.2或以上版本。)
; 分號作為注釋的開始
; Clojure 用一種把元素用括號括起來的像列表一樣的方式來書寫,元素之間用空格隔開
; clojure 解釋器會把第一個元素當做是函數或者宏調用,其他的都作為參數
; 下面這個函數用於設置當前的命名空間
(ns test)
; 更多基本的例子:
; str 函數會用它所有的參數創造一個字符串
(str "Hello" " " "World") ; => "Hello World"
; 數學運算很直觀,不過是前綴表達式
(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 1 2) ; => 2
(/ 2 1) ; => 2
; 相等比較使用 “=”符號
(= 1 1) ; => true
(= 2 1) ; => false
; 你也不必擔心邏輯運算
(not true) ; => false
; 嵌套方式正如你預料的那樣
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
; 類型系統
;;;;;;;;;;;;;
; Clojure 使用java對象類型來表示 布爾值、字符串和數字
; 使用 `class`函數來檢測它們.
(class 1) ; 整形字面值默認是java中的Long類型
(class 1.); 浮點字面值對應著java中的Double類型
(class ""); 字符串總是用雙引號括起來,並且對應著java中的Sring類型
(class false) ;布爾值對應著java中的Boolean類型
(class nil); null值被稱為 nil(英語含義:無、零點)
; 如果你想創建一列數據字面值, 使用一個單引號 ' 來防表達式被解析執行
'(+ 1 2) ; => (+ 1 2) ;這裡沒有返回3
; (上面表達式和(quote (+ 1 2)) 等價,不過更簡潔
; 你可以運算一個引用列表
(eval '(+ 1 2)) ; => 3
; 集合和序列
;;;;;;;;;;;;;;;;;;;
; 向量和列表也是java類哦!!
(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList
;書寫一個列表形如(1 2 3)一樣簡單, 但是我們不得不把它“引”(前面加個單引號)起來
;這樣就能防止解釋器把它當做一個函數來解析
;另外,(list 1 2 3) 和 '(1 2 3) 等價
;列表和向量都是集合:
(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true
; 只有列表是序列.(序列是有順序的)
(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false
; 序列是列表一種邏輯上的接口,可以懶加載.
; "懶" 意味著可以定義無窮序列,就像下面一樣:
(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追加一個元素到列表的頭部,或者向量的尾部,
(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 來進行列表計算
(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)
; 使用reduce 來進行化繁為簡 (map/reduce 思想就來自於lisp)
(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10
; Reduce 可以使用一個初始值
(reduce conj [] '(3 2 1))
; = (conj (conj (conj [] 3) 2) 1)
; => [3 2 1]
; 函數
;;;;;;;;;;;;;;;;;;;;;
; 使用fn來創建一個函數。所有的函數都有返回值,就是它的最後一個表達式
(fn [] "Hello World") ; => fn
; (你需要額外的括號去調用它)
((fn [] "Hello World")) ; => "Hello World"
;你可以使用def來創建變量
(def x 1)
x ; => 1
; 將函數賦值給一個變量
(def hello-world (fn [] "Hello World"))
(hello-world) ; => "Hello World"
; 你可以使用defn來簡化定義過程
(defn hello-world [] "Hello World")
;[] 是函數的參數列表
(defn hello [name]
(str "Hello " name))
(hello "Steve") ; => "Hello Steve"
; 你也可以使用下面這種簡寫方式
(def hello2 #(str "Hello " %1))
(hello2 "Fanny") ; => "Hello Fanny"
; 你可以創建擁有可變參數的函數
(defn hello3
([] "Hello World")
([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"
; 函數允許將參數打包成列表 (有點類似python中的*)
(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"
; 哈希表
;;;;;;;;;;
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
; 關鍵字類似字符串,但是做了一些性能上的優化
(class :a) ; => clojure.lang.Keyword
; Maps 的鍵可以是任意類型,但是通常推薦使用keywords
(def stringmap (hash-map "a" 1, "b" 2, "c" 3))
stringmap ; => {"a" 1, "b" 2, "c" 3}
(def keymap (hash-map :a 1 :b 2 :c 3))
keymap ; => {:a 1, :c 3, :b 2} (不保證順序)
; 順便說一下, 逗號只是為了看著更清晰,其他都和空格一樣,什麼都不做.
; 從一個map中檢索一個值,可以直接把這個map當做函數調用(這個NB)
(stringmap "a") ; => 1
(keymap :a) ; => 1
; 關鍵字也可以當做函數來調用,從一個map中檢索值(這個更NB)
(:b keymap) ; => 2
; stings 可沒有這個功能,所以下面會拋出異常。(這也是為什麼推薦使用keywords)
;("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn
; 檢索一個不存在的值會返回nil
(stringmap "d") ; => nil
; 使用assoc 向一個map中添加新的鍵值對。
(assoc keymap :d 4) ; => {:a 1, :b 2, :c 3, :d 4}
; 請記住, clojure 類型是不可變的!
keymap ; => {:a 1, :b 2, :c 3}
; 使用dissoc 來刪除key(可以刪除多個)
(dissoc keymap :a :b) ; => {:c 3}
; 集合
;;;;;;
(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}
; 使用con來添加新值
(conj #{1 2 3} 4) ; => #{1 2 3 4}
; 使用disj刪除原有值
(disj #{1 2 3} 1) ; => #{2 3}
; 直接將set當做函數來測試是否包含某個值(NB)
(#{1 2 3} 1) ; => 1 (有就返回原有的值)
(#{1 2 3} 4) ; => nil (沒有就返回nil)
; clojure.sets 命名空間包含更多的函數
; 一些有用的形式
;;;;;;;;;;;;;;;;;
; clojure中的邏輯結構都是宏, 看起來也沒什麼不同
(if false "a" "b") ; => "b"
(if false "a") ; => nil
; 使用let 來創建臨時綁定
(let [a 1 b 2]
(> a b)) ; => false
; 執行多條語句,返回最後一條語句
(do
(print "Hello")
"World") ; => "World" (prints "Hello")
; 所有的函數都包含一個隱式的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”來獲得一個模塊中所有的函數
(use 'clojure.set)
; 現在我們可以使用集合操作
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3} 求交集
(difference #{1 2 3} #{2 3 4}) ; => #{1} 求差集
; 你可以只導入一個函數子集(例如下面只包含交集函數)
(use '[clojure.set :only [intersection]])
; 使用reqire來導入一個模塊
(require 'clojure.string)
; 使用/從一個模塊中調用函數
(clojure.string/blank? "") ; => true
; 你可以在導入模塊的時候自定義名稱
(require '[clojure.string :as str])
(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
; (#"" denotes a regular expression literal)
; 你可以使用":require" 從一個命名空間中引入模塊(use也可以,但是別這麼做)
; 如果你使用:require的話,就沒必要把模塊“引”(前面加個單引號)起來了.
(ns test
(:require
[clojure.string :as str]
[clojure.set :as set]))
; Java
;;;;;;;;;;;;;;;;;
; java 擁有一個龐大的各種用途的標准庫,你一定迫不及待想學習如何在clojure中使用這些庫
; 使用import類引入java模塊(這個還好沒變化)
(import java.util.Date)
; 你也可以從一個命名空間中引入
(ns test
(:import java.util.Date
java.util.Calendar))
; 類名字後加個”."用來創建一個對象
(Date.) ; <a date object>
; 使用. 來調用方法. 或者使用“.方法名"簡寫的方式
(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.)) ; 和上面一樣哦
; 使用/ 來調用靜態方法
(System/currentTimeMillis) ; <a timestamp> (system is always present)
; 使用 doto 來處理可變的類<span >,所有的函數始終用最初的那個對象值,最後還是返回最初的那個對象</span> (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
Clojure 的詳細介紹:請點這裡
Clojure 的下載地址:請點這裡