April 5, 2023
By: Kevin

Clojure的一些小技巧

  1. #_ 是一个reader special form, 用来跳过下一个表达式, 可以叠在一起用
  2. as-> 嵌入 -> 来使用
  3. doto 不仅仅是用来和Java做interop的
  4. 使用 some-> 来避免null exception
  5. 临时定义个变量
  6. 使用ex-info来抛出异常
  7. 自动生成的私有类成员访问Macro
  8. 使用juxt
  9. 使用some来提前结束
  10. set可以当成函数来用, 起过滤作用
  11. 使用name函数来规格化string ,symbol,以及keyword
  12. 使用map-indexed来处理vector
  13. condp + regex

技巧, 算不上最佳实践, 用到时候会为自己的的聪明开心一下.

#_ 是一个reader special form, 用来跳过下一个表达式, 可以叠在一起用

(let [#_#_
      a 1
      b 2]
  [b])

;;相当于

(let [#_a #_1
      b 2]
  [b])

值得说明一下的是 #_并不是注释. (comment ...) 求值的结果是nil, 而 #_ 意思是跳过下个表达式.

as-> 嵌入 -> 来使用

(-> [10 11]
    (conj 12)
    (as-> xs (map - xs [3 2 1]))
    (reverse))

;; 相当于

(-> [10 11]
    (conj 12)
    (#(map - % [3 2 1]))
    (reverse))

doto 不仅仅是用来和Java做interop的

(doto 'my.test-ns require in-ns clojure.test/run-tests)

使用 some-> 来避免null exception

现代语言中, C#, swift都有?操作符(safe navigation operator), some->可以起到同样的效果.

(some-> pane .getParent .getChildren (.remove pane)))

some->>当然也是一样的.

临时定义个变量

(defn awesome [x]
  (def debug-last-input x)
  ; your decomplected goodness here
  (throw (Exception. "some crazy bug that is hard to reproduce"))

clojure1.10以后, tap>是个更好的选项.

使用ex-info来抛出异常

(throw (ex-info "some crazy bug that is hard to reproduce" {}))

ex-info 在clj/cljs均可用

自动生成的私有类成员访问Macro

(defmacro expose-private-accessors
   "Given a java class, `klass`, and a seq of symbols defining assumably private
    fields in said class, returns a map of keyworded getters and setters, that
    associate with functions that operate on said object to access the fields.
    Yields public function definitions of the form set-fieldname, get-fieldname
    for each field."
   [klass & fields]
   (let [m (with-meta (gensym "m" ) {:tag 'java.lang.reflect.Field})
         o (with-meta (gensym "o")  {:tag klass})
         fld-get-sets (vec (for [f fields]
                             [(str f)
                              (symbol (str "get-" f))
                              (symbol (str "set-" f))]))]
     `(do ~@
                   (for [[fld getter setter]  fld-get-sets]
                     `(let [~m   (.getDeclaredField ~klass  ~fld)
                            ~'_  (.setAccessible ~m true)]
                        (defn ~getter [~o]    (.get ~m ~o))
                        (defn ~setter [~o v#] (do (.set ~m ~o v#) ~o)))))))

使用juxt

(def separate (juxt filter remove))

(separate pos? [1 2 3 -1 0 4 -2])
;; => [(1 2 3 4) (-1 0 -2)]

使用some来提前结束

找到第一个满足条件的就返回

(some #(when (zero? (rem % 7)) %) (range 50 10000000000000000000))
;; => 56

keep则起到过滤的作用, 会处理输入序列中的每一个值

(keep #(when (zero? (rem % 7)) (str "value: " %)) (range 50 100))
;; => ("value: 56" "value: 63" "value: 70" "value: 77" "value: 84" "value: 91" "value: 98")

set可以当成函数来用, 起过滤作用

(filter (comp #{"one" "three"} :id)
        [{:id "one"}
         {:id "two"}
         {:id "three"}
         {:id "four"}])
=> ({:id "one"} {:id "three"})

使用name函数来规格化string ,symbol,以及keyword

=>(map (comp keyword name) [:hello 'world "!"])
(:hello :world :!)

使用map-indexed来处理vector

(for [[idx v] (map-indexed vector [:some :elements :of :seq])]
  {:idx idx :v v})
;; => ({:idx 0, :v :some} {:idx 1, :v :elements} {:idx 2, :v :of} {:idx 3, :v :seq})

(->> [:some :elements :of :seq]
     (map-indexed vector)
     (into {}))
;; => {0 :some, 1 :elements, 2 :of, 3 :seq}

condp + regex

(let [input "Alexa, set a timer for 10 seconds"]
  (condp re-matches input
    #"Alexa, turn on (.*)" :>> (fn [[_ thing]] [:turn-on thing])
    #"Alexa, turn off (.*)" :>> (fn [[_ thing]] [:turn-off thing])
    #"Alexa, set a timer for (.*) (minutes|seconds)"
    :>> (fn [[_ amount units]] [:start-timer amount units])
    #"Alexa, what's the weather like (.*)"
    :>> (fn [[_ when]]
          (case when
            "today" [:weather-today]
            "tomorrow" [:weather-tomorrow]))))
;; => [:start-timer "10" "seconds"]
Tags: clojure