Y分钟学clojure
Clojure是運行在JVM上的Lisp家族中的一員。她比Common Lisp更強調純函數式編程,且自發布時便包含了一組工具來處理狀態。
這種組合讓她能十分簡單且自動地處理并發問題。
(你需要使用Clojure 1.2或更新的發行版)
; 注釋以分號開始。 Clojure代碼由一個個form組成, 即寫在小括號里的由空格分開的一組語句。
Clojure解釋器會把第一個元素當做一個函數或者宏來調用,其余的被認為是參數。
Clojure代碼的第一條語句一般是用ns來指定當前的命名空間。
(ns learnclojure)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嵌套的form工作起來應該和你預想的一樣
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2類型
Clojure使用Java的Object來描述布爾值、字符串和數字
用函數?class?來查看具體的類型
如果你想創建一組數據字面量,用單引號(')來阻止form被解析和求值
'(+ 1 2) ; => (+ 1 2)單引號是quote的簡寫形式,故上式等價于(quote (+ 1 2))
可以對一個引用列表求值
(eval '(+ 1 2)) ; => 3集合(Collection)和序列
List的底層實現是鏈表,Vector的底層實現是數組
二者也都是java類
list本可以寫成(1 2 3), 但必須用引用來避免被解釋器當做函數來求值。
(list 1 2 3)等價于'(1 2 3)
集合其實就是一組數據
List和Vector都是集合:
序列 (seqs) 是數據列表的抽象描述
只有列表才可稱作序列。
序列被訪問時只需要提供一個值,所以序列可以被惰性加載——也就意味著可以定義一個無限序列:
(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將以最高效的方式向集合中添加元素。
對于列表,數據會在起始位置插入,而對于向量,則在末尾位置插入。
用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)recuce使用函數來規約集合
(reduce + [1 2 3 4]) ; = (+ (+ (+ 1 2) 3) 4) ; => 10reduce還能指定一個初始參數
(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來創建一個變量(var)
(def x 1) x ; => 1將函數定義為一個變量(var)
(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"可以定義變參函數,即把&后面的參數全部放入一個序列
(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和基于數組的map(即arraymap)實現了相同的接口,hashmap查詢起來比較快,
但不保證元素的順序。
arraymap在足夠大的時候,大多數操作會將其自動轉換成hashmap,
所以不用擔心(對大的arraymap的查詢性能)。
map支持很多類型的key,但推薦使用keyword類型
keyword類型和字符串類似,但做了一些優化。
順便說一下,map里的逗號是可有可無的,作用只是提高map的可讀性。
從map中查找元素就像把map名作為函數調用一樣。
(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會返回nil。
(stringmap "d") ; => nil用assoc函數來向hashmap里添加元素
(def newkeymap (assoc keymap :d 4)) newkeymap ; => {:a 1, :b 2, :c 3, :d 4}但是要記住的是clojure的數據類型是不可變的!
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}把集合當做函數調用來檢查元素是否存在:
(#{1 2 3} 1) ; => 1 (#{1 2 3} 4) ; => nil在clojure.sets模塊下有很多相關函數。
常用的form
clojure里的邏輯控制結構都是用宏(macro)實現的,這在語法上看起來沒什么不同。
(if false "a" "b") ; => "b" (if false "a") ; => nil用let來創建臨時的綁定變量。
(let [a 1 b 2](> a b)) ; => false用do將多個語句組合在一起依次執行
(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)然后就可以使用set相關的函數了
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3} (difference #{1 2 3} #{2 3 4}) ; => #{1}你也可以從一個模塊里導入一部分函數。
(use '[clojure.set :only [intersection]])用require來導入一個模塊
(require 'clojure.string) 用/來調用模塊里的函數
下面是從模塊clojure.string里調用blank?函數。
在import里你可以給模塊名指定一個較短的別名。
(require '[clojure.string :as str]) (str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."#""用來表示一個正則表達式
你可以在一個namespace定義里用:require的方式來require(或use,但最好不要用)模塊。
這樣的話你無需引用模塊列表。
Java
Java有大量的優秀的庫,你肯定想學會如何用clojure來使用這些Java庫。
用import來導入java類
(import java.util.Date)也可以在ns定義里導入
(ns test(:import java.util.Datejava.util.Calendar))用類名末尾加.的方式來new一個Java對象
(Date.) ; <a date object>用.操作符來調用方法,或者用.method的簡化方式。
(. (Date.) getTime) ; <a timestamp> (.getTime (Date.)) ; 和上例一樣。用/調用靜態方法
(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:00STM
軟件內存事務(Software Transactional Memory)被clojure用來處理持久化的狀態。
clojure里內置了一些結構來使用STM。
atom是最簡單的。給它傳一個初始值
用swap!更新atom。
swap!會以atom的當前值為第一個參數來調用一個指定的函數,
swap其余的參數作為該函數的第二個參數。
用@讀取atom的值
my-atom ;=> Atom<#...> (返回Atom對象) @my-atom ; => {:a 1 :b 2}下例是一個使用atom實現的簡單計數器
(def counter (atom 0)) (defn inc-counter [](swap! counter inc))(inc-counter) (inc-counter) (inc-counter) (inc-counter) (inc-counter)@counter ; => 5 其他STM相關的結構是ref和agent.
; Refs:?http://clojure.org/refs
; Agents:?http://clojure.org/agents
進階讀物
本文肯定不足以講述關于clojure的一切,但是希望足以讓你邁出第一步。
Clojure.org官網有很多文章:
http://clojure.org/
Clojuredocs.org有大多數核心函數的文檔,還帶了示例哦:
http://clojuredocs.org/quickref/Clojure%20Core
4Clojure是個很贊的用來練習clojure/FP技能的地方:
http://www.4clojure.com/
Clojure-doc.org 有很多入門級的文章:
http://clojure-doc.org/
總結
以上是生活随笔為你收集整理的Y分钟学clojure的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三个小问题
- 下一篇: Clojure 入门