語法糖不少, 就是奔這個「懶」 來用clj的.node
可是,在常見的書裏(《Clojure編程》《Clojure編程樂趣2》)都對不少基本語法,用法都介紹不全, 不細。看書看得很累。es6
好比《Clojure編程》裏 第1章介紹了各類基本語法,可是沒有介紹for :when,而後在P138 直接用了正則表達式
(for [dx [-1 0 1] dy [-1 0 1] :when (not= 0 dx dy)] [(+ dx x) (+ dy y)])
也沒有詳細解釋。也許不是一個做者寫的吧。編程
對我這種特別笨的人來講,一下就看不懂了,感受仍是有不少坑。數據結構
必須對照https://clojuredocs.org/ + 本身嘗試。app
寫在前面。ide
在已經學過《SICP》和用過scheme和hy的基礎上,感受Clojure確實有本身的特色。仍然必須先讓本身進入什麼都不會的狀態,耐心去學,才能真學進去,否則,小問題就能卡住半天。還不容易找到答案。函數
和只有7個關鍵字scheme不一樣,Clojure系各類語法糖特別多,並且由於引入的不一樣的集合#{} {} [] '(),客觀上帶來了不少scheme裏沒有的「轉型"問題。oop
若是不從細微出入手,別說讀懂句子,就連每一個詞,每一個字,甚至每一個部首,都不認識。>_<!性能
開始學偏旁部首,認字過程 學的很是慢,都是很是細節,功能單一可是很是細膩的小函數,小動詞。
而後用這些部首本身去造字組詞(DSL),而後再去用DSL組詞造句表達。
scheme裏一切皆列表 list() 頂多知道cons 也就差很少了。
數據類型,頂多知道有symbol類型 就已經能夠寫出個解釋器了。
hy/py 也就是py裏的set {} list [] map {k:v} 而後tuple() generator () ...大概也就差很少了
可是Clojure的數據結構由於 不可變/可變 的分野,幾乎等於類型數量×2。
即便不考慮這一點,其餘性能、實現方面的複雜度,工程上和JVM的兼容等等
Clojure放棄了scheme裏一切皆括號()的寫法, 引入{} [] 。客觀上放眼望去,不是無盡的括號,可讀性加強。
可是弊端固然就是學習、寫碼時負載增長。
典型體如今:
1必須認真理解() 和 [] 的差別,包括conj行爲差別,遍歷性能差別等等等。
2 必須記住(for []) 返回的是個list () 須要別的類型,還要轉。
想直接返回1個list 是麻煩的,這樣寫是不行的
[for [l [1 2 3]] (* l 2)]
由於外層不能是方括號
而在py裏 list 表達式根深蒂固。而hy裏,咱們有(lfor ) 也能直接返回list。我的認爲hy的lfor方案很好。
3 不得不依賴 引入各類轉型 好比filter 以外,還搞出了filterv filterm 分別對應返回結果是 vector map 的狀況。
我的感受,這裏還不如py優雅。
再好比,合併([] [] [])-> [] nested vector成爲1個vector
大概只能
(vec (apply concat nested_coll))
雖然把這一行本身寫成宏也沒什麼,可是總感受怪異,不優雅。
-----------------------------------------分割線---------------------------------------------------------------
下面全都是我記錄下在py hy裏沒有的,我這種菜鳥新手不太習慣的寫法。陸續增長。
上面這句裏, for能夠同時循環dx dy 2個變量 至關於2個for嵌套
後面的:when 保證只有when成立時再執行body
注意 若是執行函數,要用這個,而不是for
(doseq [x [0 1]] (println "aa") )
這裏doseq 換成for 裏面的println 是不會被執行的。這裏區分有點細,和py的list表達式和 hy區別
not=能夠接受多個參數,來判斷連續相等。至關於+ - * / 連加
由於集合能夠做爲函數,因此固然能夠做爲謂詞函數
(if (#{3 2}, 0) "真" "假")
這個集合#{3 2}徹底能夠定義爲1個謂詞函數,就像《Clojure編程》P143同樣,在body裏把集合定義成謂詞,而後在外面簡單把集合傳進來
內部
(if (survive? 0) "真" "假")
其實survive?這個看起來是謂詞函數的東西,只是個簡單的#{2 3}
(mapcat f p) 等於(concat (map f p)) 把map的結果鏈接起來
從scheme,py pandas裏就都有。但總記不住
(apply f [p1 p2 p3])
(map f [p1 p2 p3])
相同點: 都是緊跟1個函數。後面是一串參數。
不一樣點:map: 是「映射」,因此返回((f p1) (f p2) (f p2))
apply 的f 是接受3個參數的, 返回(f p1 p2 p3) 好比
(apply + [1 2 3])
=>6
(map #(* % 2) [1 2 3]) =>(2 4 6)
apply還有一個重要做用就是「脫括號」的做用。好比 當參數是[[], []] 這種時,想用concat 把nest2個vector鏈接起來,用
(apply concat [vec1, vec2])
至關於(concat vec1 vec2)
——微吐槽:不如py的 list extend() 或者*解引用 itertools.chain(*[vec1 vec2])
把元素添加進set
user=> (disj #{1 2 3}) ; disjoin nothing #{1 2 3} user=> (disj #{1 2 3} 2) ; disjoin 2 #{1 3} user=> (disj #{1 2 3} 4) ; disjoin non-existent item #{1 2 3} user=> (disj #{1 2 3} 1 3) ; disjoin several items at once #{2}
和map相同點:都是元素級操做
不一樣點:
map:1個函數,多個參數;
juxt多個函數,1個參數。
((juxt a b c) x) => [(a x) (b x) (c x)]
返回1個函數,這個函數能夠接收任意數量的參數,但永遠返回初始給定的返回值
user=> (def boring (constantly 10)) #'user/boring user=> (boring 1 2 3) 10 user=> (boring) 10 user=> (boring "Is anybody home?") 10
來自《FUNCTIONAL PEARL》中的概念。
以不可變的方式遍歷層次數據結構(如嵌套的vector XML 等等等)。
參考
http://josf.info/blog/2014/03/21/getting-acquainted-with-clojure-zippers/
http://www.thattommyhall.com/2013/08/23/genetic-programming-in-clojure-with-zippers/
(zipper branch? children make-node root) ;; Creates a new zipper structure. ;; branch? is a fn that, given a node, returns true if can have ;; children, even if it currently doesn't. ;; children is a fn that, given a branch node, returns a seq of its ;; children. ;; make-node is a fn that, given an existing node and a seq of ;; children, returns a new branch node with the supplied children. ;; root is the root node.
相似但不一樣。主要是返回值
into
(into [1 2] [3 4]) =>[1 2 3 4]
concat
(concat [1 2] [3 4]) =>(1 2 3 4)
選入進新的collection
user=> (into (sorted-map) [ [:a 1] [:c 3] [:b 2] ] ) {:a 1, :b 2, :c 3} user=> (into (sorted-map) [ {:a 1} {:c 3} {:b 2} ] ) {:a 1, :b 2, :c 3} ; When maps are the input source, they convert into an unordered sequence ; of key-value pairs, encoded as 2-vectors user=> (into [] {1 2, 3 4}) [[1 2] [3 4]]
user=> (into () '(1 2 3)) (3 2 1) ; This does not happen for a vector, however, due to the behavior of conj: user=> (into [1 2 3] '(4 5 6)) [1 2 3 4 5 6]
多個map規約爲1個map 把每一個map的 value 按 f進行規約
(merge-with f & maps)
(merge-with into {"Lisp" ["Common Lisp" "Clojure"] "ML" ["Caml" "Objective Caml"]} {"Lisp" ["Scheme"] "ML" ["Standard ML"]}) ;;=> {"Lisp" ["Common Lisp" "Clojure" "Scheme"], "ML" ["Caml" "Objective Caml" "Standard ML"]}
(merge-with + {:a 1 :b 2} {:a 9 :b 98 :c 0} {:a 10 :b 100 :c 10} {:a 5} {:c 5 :d 42}) ;;=> {:d 42, :c 15, :a 25, :b 200}
能夠把多個key value 放進一個map裏, 把map做爲可變對象
(assoc map key val) (assoc map key val & kvs) (assoc {} :key1 "value" :key2 "another value") ;;=> {:key2 "another value", :key1 "value"}
把多個element 加入conj到coll。 返回新coll,不可變對象
注意,只保證加入。順序性各有不一樣:
map和set沒有順序不談
[] vector在末尾添加
'() 列表在頭部添加
(conj coll x) (conj coll x & xs) ;; notice that conjoining to a vector is done at the end (conj [1 2 3] 4) ;;=> [1 2 3 4] ;; notice conjoining to a list is done at the beginning (conj '(1 2 3) 4) ;;=> (4 1 2 3) (conj ["a" "b" "c"] "d") ;;=> ["a" "b" "c" "d"] ;; conjoining multiple items is done in order (conj [1 2] 3 4) ;;=> [1 2 3 4] (conj '(1 2) 3 4) ;;=> (4 3 1 2) (conj [[1 2] [3 4]] [5 6]) ;;=> [[1 2] [3 4] [5 6]]
接受1個雙參數的pred函數,而後跟1個做爲第2個參數,
後面跟的列表,是第1個參數的列表,和值的列表。
(println (condp #(%1 %2) :foo string? "it's a string" keyword? "it's a keyword" symbol? "it's a symbol" fn? "it's a function" "something else!") ) =>it's a keyword
vector在外面加一層方括號[]
vec把外層轉換成[]
(vector '(1 2 3)) =>[(1 2 3)] (vec '(1 2 3)) =>[1 2 3]
es6裏let是和const並列的;let定義可變變量,const定義不可變變量。
Clojure裏切不可望文生義。let遠不止是用於 建立臨時變量 和es6 ts裏 的let感受徹底不同。
Clojure裏 let和def的區別是這樣分的
let建立 內部、不可變變量
def建立 namespace、可變變量
let的做用起碼有3條:
1 建立內部、不可變變量。
2 解構賦值 destruct
3 賦值有順序,後面的語句能夠調用前面的,因此能夠在let中放置順序執行語句
(let [var1 v1
var2 (f1 var1)
]
(f2 var1 var2)
)
這樣,在let 的方括號裏 [] 把var1 var2 按順序賦值好,其中var2 的賦值還用到了剛剛賦值的var1。
最後let的body裏,只表現最終返回結果就行了。
能夠認爲,多用let少用do就對了
(let [數值準備]
(返回的結果)
)
只把最後1次計算 方在body裏,或者返回 一個map{} 或者list [], 能夠突顯返回值。
這種涉及思路,至關於把所有內部變量全都在頭部聲明、賦值1次。只用1個let 避免其餘語言裏處處const var好屢次。
並且用機制保證了賦值後的不可變性。
最後,讓body聚焦於值的返回
確實是很強大,頗有力的表達方式,必定要掌握。
和py/hy不一樣 defn 定義的一概是namespace級別的。因此若是defn內部嵌入1個defn定義,則外部執行2遍,將致使內部defn也更新。(由於def能夠覆蓋),但本意內部的defn是外部不可見的局部函數。這時就用letfn
簡單說,任何涉及fn內部的東西,都要顯式用let/letfn來構造。
在Clojure裏, 局部/ns 的區別必須顯式聲明!
(for [x (range 20) :when (not= x 10)] x) ; =>(0 1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18 19) (for [x (range 20) :while (not= x 10)] x) ; => (0 1 2 3 4 5 6 7 8 9)
:when會遍歷整個循環,條件不知足的不執行 相似 continue
:while 遇到第一次知足條件的地方,就會退出。相似break
只至關於for中的:when,不能在第1次知足是停下:下面3種寫法是等效的
(for [x (range 20) :when (not= x 10)] x) (filter #(not= % 10) (for [x (range 20)] x)) (filter #(not= % 10) (range 20))
後兩種顯然最後更簡單。
不一樣點:
寫在 :when和:while 裏面的不是函數,是表達式
如何選擇:
若是過濾條件簡單,就是個簡單表達式,那麼用for更合適;
若是過濾條件是外部定義的predicator函數,那麼用filter合適;
我的傾向,儘可能使用filter/filterv/filterm,不顯式使用for和循環,尤爲coll已是外部賦值好的時候。 和須要定製返回值類型時
for的好處是,能夠在body裏對返回值進行定製。
#"" 是正則表達式 若是隻用1個分割符,就這樣
(str/split "*-D1R2" #"-")
等價於py裏
"*-D1R2".split("-")
user=> (def boring (constantly 10)) #'user/boring user=> (boring 1 2 3) 10 user=> (boring) 10 user=> (boring "Is anybody home?") 10
注意recur裏,各循環變量仍是不可變的。
(loop [iter 1 acc 0] (if (> iter 10) (println acc) (recur (inc iter) (+ acc iter)))) ;; => 55 ;; sum from 1 to 10
每次recur (inc i) ( XX i)
(loop [i 0] (let [res1 ((get fns-cmp i) env1 env2)] (if (or (>= i n) (not= 0 res1)) res1 (recur (inc i)) ) ) )
(def users [{:name "James" :age 26} {:name "John" :age 43}]) #'user/users ;; similar to assoc-in but does not simply replace the item. ;; the specified function is performed on the matching item. ;; here the age of the second (index 1) user is incremented. (update-in users [1 :age] inc) ;;=> [{:name "James", :age 26} {:name "John", :age 44}]
查詢條件是nest指向1個記錄的,因此若是想更新多於1個值,須要assoc
;;You can use update-in in a nested map too, in order to update more than ;;one value: (def m {:1 {:value 0, :active false}, :2 {:value 0, :active false}}) (update-in m [:1] assoc :value 1 :active true) ;;=>{:1 {:value 1, :active true}, :2 {:value 0, :active false}}