clojure的語法糖

語法糖不少, 就是奔這個「懶」 來用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確實有本身的特色。仍然必須先讓本身進入什麼都不會的狀態,耐心去學,才能真學進去,否則,小問題就能卡住半天。還不容易找到答案。函數

1學Clojure就像是學漢字。要從邊旁部首學起

和只有7個關鍵字scheme不一樣,Clojure系各類語法糖特別多,並且由於引入的不一樣的集合#{} {} [] '(),客觀上帶來了不少scheme裏沒有的「轉型"問題。oop

若是不從細微出入手,別說讀懂句子,就連每一個詞,每一個字,甚至每一個部首,都不認識。>_<!性能

開始學偏旁部首,認字過程 學的很是慢,都是很是細節,功能單一可是很是細膩的小函數,小動詞。

而後用這些部首本身去造字組詞(DSL),而後再去用DSL組詞造句表達。

2  數據結構、類型+遠多於其餘語言,利弊互現。

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))
1 apply concat 把([e1] [e2 e3]) 拼接成(e1 e2 e3),
2 而後vec把外層()變成[], 獲得 [e1 e2 e3]

雖然把這一行本身寫成宏也沒什麼,可是總感受怪異,不優雅。

 

-----------------------------------------分割線---------------------------------------------------------------

 

 下面全都是我記錄下在py hy裏沒有的,我這種菜鳥新手不太習慣的寫法。陸續增長。

for和doseq

上面這句裏, for能夠同時循環dx  dy 2個變量  至關於2個for嵌套

後面的:when 保證只有when成立時再執行body

注意 若是執行函數,要用這個,而不是for

(doseq [x [0 1]]
(println "aa")
)

這裏doseq 換成for 裏面的println 是不會被執行的。這裏區分有點細,和py的list表達式和 hy區別

not=

能夠接受多個參數,來判斷連續相等。至關於+ - * / 連加

 set 做爲謂詞

由於集合能夠做爲函數,因此固然能夠做爲謂詞函數

(if (#{3 2}, 0) "" "")

這個集合#{3 2}徹底能夠定義爲1個謂詞函數,就像《Clojure編程》P143同樣,在body裏把集合定義成謂詞,而後在外面簡單把集合傳進來

內部

(if (survive? 0) "" "")

其實survive?這個看起來是謂詞函數的東西,只是個簡單的#{2 3}

mapcat

(mapcat f p)    等於(concat  (map  f  p)) 把map的結果鏈接起來

apply 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])

disj

把元素添加進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}

juxt

和map相同點:都是元素級操做

不一樣點:

map:1個函數,多個參數;

juxt多個函數,1個參數。

((juxt a b c) x) => [(a x) (b x) (c x)]

 constantly

返回1個函數,這個函數能夠接收任意數量的參數,但永遠返回初始給定的返回值

user=> (def boring (constantly 10))
#'user/boring

user=> (boring 1 2 3)
10

user=> (boring)
10

user=> (boring "Is anybody home?")
10

 Zipper

 來自《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.

concat和into

相似但不一樣。主要是返回值

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]

 

merge-with

多個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}

 

assoc

能夠把多個key value 放進一個map裏, 把map做爲可變對象

(assoc map key val)
(assoc map key val & kvs)

(assoc {} :key1 "value" :key2 "another value")

;;=> {:key2 "another value", :key1 "value"}

conj

把多個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]]

 condp

接受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
(condp #( %1 2 %2) 3
        = "eq"
        < "lt"
        > "gt")

vector和vec 

 vector在外面加一層方括號[]

vec把外層轉換成[]

(vector '(1 2 3))
=>[(1 2 3)]

(vec '(1 2 3))
=>[1 2 3]

 超強的let

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聚焦於值的返回

確實是很強大,頗有力的表達方式,必定要掌握。

fn內部的letfn

和py/hy不一樣 defn 定義的一概是namespace級別的。因此若是defn內部嵌入1個defn定義,則外部執行2遍,將致使內部defn也更新。(由於def能夠覆蓋),但本意內部的defn是外部不可見的局部函數。這時就用letfn

簡單說,任何涉及fn內部的東西,都要顯式用let/letfn來構造。

在Clojure裏, 局部/ns  的區別必須顯式聲明!

 for中的:when和:while 

(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

filter

只至關於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("-")

 

identity和constantly

都是用於建立簡單函數: 
identity返回f(x)=x
 
而constantly 返回 f(x)=const  不論接收到什麼參數,永遠返回建立時給的const
user=> (def boring (constantly 10))
#'user/boring

user=> (boring 1 2 3)
10

user=> (boring)
10

user=> (boring "Is anybody home?")
10

 

 loop recur的坑

注意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)

第2個裏面不能當即用inc後的(inc iter)後的iter值
在這裏,這種機制保證告終果的正確:
在最後1次loop的時候(iter=10),雖然第一個語句(inc iter)彷佛把iter加到了11, 可是第2個語句裏(+ acc iter )仍然把iter=10送進去累加,而後跳到頭部 遇到if 打印了最終的acc。
 
因此,recur的兩次調用,用的 iter在先後語句裏不會改變! 能夠認爲loop聲明的變量仍是相似let,對開發者的代碼是不可變的;而iter值的改變在代碼recur執行完,goto過程當中,才由Clojure完成
 
只好改爲裏面嵌套let了
      (loop [i 0]
        (let [res1 ((get fns-cmp i) env1 env2)]
          (if (or (>= i n) (not= 0 res1))
            res1
            (recur (inc i))
          )
        )
      )

 

update-in

修改嵌套的數據結構內的值,有點相似mongo的查詢條件,可是簡單得多
(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}}
 
相關文章
相關標籤/搜索