clojure编程初体验

收藏的学习地址:

Leiningen中文教程
luminusweb官方文档
Clojure常用包
leiningen API
clojure常用库列表
clojure技术书籍列表
clojure语法中文教程
clojure函数式编程

学习中命令行里敲过的

(+ 11 11 1111)

(/ 1 2)

(doc /)

(Take 5 (repeat 10))

(1 2 3) => error

'(1 2 3) => (1 2 3)

(doc if)

(def a-array [1 2 3])

(def b-array [1 2 3])

(idertical? a-array b-array)

(def a-list (1 2 3))

(def b-list [1 2 3])

(type a-list)

(type b-list)

(conj a-list 9)

(conj b-list 9)

(def my-add (fn [x y] (+ x y)))

(my-add 4 2)

`#{1 2 3}`

(type #{1 2 3})

(type {:a a})

(filter even? [1 2 3 ])

(map inc [1 2 3])

常用的一些函数的使用体会

  1. map
# map  遍历list,格式化list里map的key对应的value
(require '[clj-time.format :as f])
=> nil
(def custom-formatter (f/formatter "yyyyMMdd"))
=> #'user/custom-formatter
(defn convert-format [d] (f/unparse custom-formatter (f/parse (f/formatter :date-hour-minute-second ) d)))
=> #'user/convert-format
(map (fn [m] (assoc m :create-time (convert-format (:create-time m))) )
     [{:id 1
       :create-time "2019-04-17T14:47:24"}
      {
       :id 2
       :create-time "2019-04-17T14:47:44"}])
=> ({:id 1, :create-time "20190417"} {:id 2, :create-time "20190417"})

# 在写一个
(map (fn [m] (assoc m
               :office-name (format "%s-%s" (:office-id m) (:office-name m))))
     
  [{
   :office-id "1",
   :office-name "耳鼻喉科",
   :hospital-id "1",
   :create-time "2019-04-17T14:47:24",
   :deleted 0
   },
  {
   :office-id "2",
   :office-name "变态反应科",
   :hospital-id "1",
   :create-time "2019-04-17T14:47:44",
   :deleted 0
   }])
=>
({:office-id "1", :office-name "1-耳鼻喉科", :hospital-id "1", :create-time "2019-04-17T14:47:24", :deleted 0}
 {:office-id "2", :office-name "2-变态反应科", :hospital-id "1", :create-time "2019-04-17T14:47:44", :deleted 0})

map的解构

user> (def my-map {:a 1 :b 2 :c 3})
#'user/my-map
user> (let [{x :a y :c} my-map]
  (println ":a val:" x ", :c val: " y))
:a val: 1 , :c val:  3
nil


user> (let [{:keys [a c]} my-map]
  (println ":a val:" a ", :c val: " c))
:a val: 1 , :c val:  3
nil


user> (let [{:strs [foo bar]} {"foo" 1 "bar" 2}]
  (println "FOO:" foo  "BAR: " bar ))
FOO: 1 BAR:  2
nil


user> (let [{:syms [foo bar]} {'foo 1 'bar 2}]
        (println "FOO:" foo "BAR:" bar))
FOO: 1 BAR: 2
nil


user> (def data
  {:foo {:a 1
         :b 2}
   :bar {:a 10
         :b 20}})

(let [{{:keys [a b]} :foo
       {a2 :a b2 :b} :bar} data]
  [a b a2 b2])
#'user/data[1 2 10 20]

user> (def my-map {:a 3 :b 4})
(let [{a :a
       b :b
       :keys [c d]
       :or {a 1
            c 2}} my-map]
  (println a b c d))
#'user/my-map3 4 2 nil

2、date、timestamp、string

(import '[java.text SimpleDateFormat]) 
(import '[java.sql Timestamp]) 

;; timelong is the long int time seconds 
(defn timestamp2date [timelong] 
(.toString (Timestamp. timelong))) 

;; date is date string 04/16/2012 
(defn date2timestamp [date] 
(.getTime (.parse (SimpleDateFormat. "MM/dd/yyyy") date)))

3、conj
4、seq
5、when-let
6、comp
就是对参数从右到左组合执行所有函数,如下面的函数:

((comp f1 f2 .. fn) arg1 arg2 .. argn)  

可以转变为:

(f1 (f2 (.. (fn arg1 arg2 .. argn))))  

举例

((comp str +) 8 8 8)   
;;=> "24"
(filter (comp not zero?) [0 1 0 2 0 3 0 4])
;;=> (1 2 3 4)
(map
   (comp - (partial + 3) (partial * 2))
       [1 2 3 4])
;;=>  (-5 -7 -9 -11)

7、partial
形如:((partial f arg1 arg2 .. argn) arga argb .. argz)
就是执行:
(f arg1 arg2 .. argn arga argb .. argz)
注意:偏函数的第一个参数是一个函数,后面至少有1个其他参数
partial函数称为“偏函数”或者“部分完整函数”,因为它是不完整的,定义也用def而不是defn。

user=> (def hundred-times (partial * 100))
#'user/hundred-times

user=> (hundred-times 5)
500

user=> (hundred-times 4 5 6)
12000

8、reduce
9、apply

函数间对比使用

1、assoc vs update-in vs assoc-in
说法不一,详情参考Assoc-vs-Update/
assoc,只允许更新数据结构的第一层。处理数据的性能最高,几乎是assoc-in的两倍。
assoc-in 和update-in 可以使用路径表达式改变数据结构的内层。
如果新的值不依赖旧值,assoc-in就可以满足需求,不需要使用update-in;在有依赖关系时,使用update-in

(defrecord Person [fname lname address])
(defrecourd Address [street city state zip])

(def stu (Person. "Stu" "Halloway" (Address. "200 N Mangum" "Durham" "NC" 27707)))
(assoc stu :fname "Stuart")
(upate-in stu [:address :zip] inc)

有用的代码段

1、记录接口响应时间middleware

(defn record-response-time [handler]
  (fn [req]
    (let [start-date (System/currentTimeMillis)]
      (handler req)
      (let [res-time (- (System/currentTimeMillis) start-date)]
        (println (format  "%s took %d ms" (:uri req) res-time))))))

需要注意的是 record-response-time 需要放在 middleware 最外层,这样它才能纪录一个请求经过所有 middleware + handler 处理的时间。


image.png

2、移除map中值为nil的key
参考clojure nil? - Remove nil values from a map?
我使用的两个

(into {} (filter #(not (nil? (val %))) {:a true :b false :c nil}))
(into {} (remove #(nil? (val %)) {:a true :b false :c nil}))

3、判断字符串包含
参考:https://stackoverflow.com/questions/26386766/check-if-string-contains-substring-in-clojure
使用(.contains "The Band Named Isis" "Isis")或者(clojure.string/includes? "abc" "ab")
The easiest way is to use the contains method from java.lang.String:

(.contains "The Band Named Isis" "Isis")

=> true

You can also do it with regular expressions, e.g.

(re-find #"Isis" "The Band Named Isis")
=> "Isis"

(re-find #"Osiris" "The Band Named Isis")
=> nil

If you your result to be true or false, you can wrap it in boolean:

(boolean (re-find #"Osiris" "The Band Named Isis"))

=> false

(use '[clojure.string :as s])
(s/includes? "abc" "ab") ; true
(s/includes? "abc" "cd") ; false

以下规范来自:https://blog.csdn.net/zdplife/article/details/51534182
4、切割或者拼接vector
最后想要谈到的就是函数效率问题,在transient(http://blog.csdn.net/zdplife/article/details/52138512)那篇文章中介绍了,clojure中有些函数使用该数据特性可以提高效率,所以我们也尽量选择使用一些利用了transient特性的函数,尤其在效率要求比较高的工程中:

;;因为pop函数使用了transient数据结构,所以尽量使用第二种写法:
(vec (take 2 [1 2 3]))
;;=> [1 2]
(pop [1 2 3])
;;=> [1 2]
;;因为into函数使用了transient数据结构,所以尽量使用第二种写法:
(vec (concat [1 2] [3 4]))
;;=> [1 2 3 4]
(into [1 2] [3 4])
;;=> [1 2 3 4]

5、vector和list的操作:

  • 给seq增加元素:conj和cons
    cons函数不管是向什么类型的序列中插入元素都会放在序列的头上,而conj函数在插入元素时,会考虑到原有序列类型的插入效率,因为list支持头部高速插入,所以会放在list头部,而如果是vector则会放到其尾部

  • 删除seq里的元素
    删除序列中的元素常用的函数有rest,pop,subvec,其中rest函数类似cons是序列操作函数,返回的是一个序列类型,而pop函数会保持原有数据类型不变,subvec函数对vector操作,返回原有类型,它创建的向量与原来的内部结构一样,非常有效,执行时间为常数。

  • 从vector中获取元素


    image.png
  • 修改元素用update或者assoc

6、函数体内偏好使用 pre 函数与 post 条件来检查。

(def bar inc)

;; good
(defn foo [x]
  {:pre [(pos? x)]}
  (bar x))
 
;; bad
(defn foo [x]
  (if (pos? x)
    (bar x)
    (throw (IllegalArgumentException "x must be a positive number!")))

7、使用 seq 作为终止条件来测试序列是否为空(这个技巧有时候称为 nil punning)。

;; good
(defn print-seq [s]
  (when (seq s)
    (prn (first s))
    (recur (rest s))))
 
;; bad
(defn print-seq [s]
  (when-not (empty? s)
    (prn (first s))
    (recur (rest s))))

8、巧用补足函数complement

(map (complement even?) '(1 2 3 4))

;; return...
;; (true false true false)

;; see also ...
(map even? '(1 2 3 4))
;; (false true false true)

;; WORNING
;; This function returns ERROR!!!
(map (not even?) '(1 2 3 4))

(map #(not (even? %)) '(1 2 3 4)) ; This works

9、comp,partial让代码更简洁

;; good
(map #(capitalize (trim %)) ["top " " test "])
 
;; better
(map (comp capitalize trim) ["top " " test "])

;; good
(map #(+ 5 %) (range 1 10))
 
;; (arguably) better
(map (partial + 5) (range 1 10))

10、用condp代替cond,当表达式是编译期间常量,应该用case

;; good
(cond
  (= x 10) :ten
  (= x 20) :twenty
  (= x 30) :forty
  :else :dunno)
 
;; better
(condp = x
  10 :ten
  20 :twenty
  30 :forty
  :dunno)
 
;; best
(case x
  10 :ten
  20 :twenty
  30 :forty
  :dunno)

11、使用包装好的java interop

;;; object creation
;; good
(java.util.ArrayList. 100)
 
;; bad
(new java.util.ArrayList 100)
 
;;; static method invocation
;; good
(Math/pow 2 10)
 
;; bad
(. Math pow 2 10)
 
;;; instance method invocation
;; good
(.substring "hello" 1 3)
 
;; bad
(. "hello" substring 1 3)
 
;;; static field access
;; good
Integer/MAX_VALUE
 
;; bad
(. Integer MAX_VALUE)
 
;;; instance field access
;; good
(.someField some-object)
 
;; bad
(. some-object some-field)

12、利用关键字可以当作复合类型的函数这个事实。

((juxt :a :b) {:a "ala" :b "bala"})
;;["ala" "bala"]

13、vector数组转字符串

(clojure.string/join "," ["123" "234" "234"])
=>"123,234,234"

反过来就得用clojure.string.split这个函数了

(clojure.string/split "Clojure is awesome!" #" ")
["Clojure" "is" "awesome!"]

你可能感兴趣的:(clojure编程初体验)