语法糖不少, 就是奔这个“懒” 来用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}}