跳转到内容

Clojure 编程/示例/食谱

来自维基教科书,开放世界中的开放书籍

如何在函数参数列表或 let 绑定中进行结构化绑定或解构。

[编辑 | 编辑源代码]

而不是像这样访问映射中的值

(defn list-xyz [xyz-map]
  (list (:x xyz-map) (:y xyz-map) (:z xyz-map)))

user=> (list-xyz {:x 1, :y 2 :z 3})
(1 2 3)

你可以像这样解构映射

(defn list-xyz [{x :x, y :y, z :z}]
  (list x y z))

user=> (list-xyz {:x 1, :y 2, :z 3})
(1 2 3)

或者更好的是,像这样

(defn list-xyz [{:keys [x, y, z]}]
  (list x y z))

user=> (list-xyz {:x 1, :y 2 :z 3})
(1 2 3)

而不是像这样访问向量元素

(defn vec-norm [vec3]
  (Math/sqrt (+ (* (nth vec3 0) (nth vec3 0))
		(* (nth vec3 1) (nth vec3 1))
		(* (nth vec3 2) (nth vec3 2)))))

你可以像这样解构向量

(defn vec-norm [[x, y, z]]
  (Math/sqrt (+ (* x x) (* y y) (* z z))))

user=> (vec-norm [1, 2, 3])
3.7416573867739413

循环遍历序列中的项

[编辑 | 编辑源代码]
user=> (doseq [ item '((1 2) [3 4] "D")] (prn item))
(1 2)
[3 4]
"D"
nil

user=> (doseq [item {:a 1 :b 2}] (prn item))
[:b 2]
[:a 1]

user=> (for [item {:a 1 :b 2}] item)
([:a 1] [:b 2])

for 循环

[编辑 | 编辑源代码]
user=> (dotimes [i 4] (prn i))
0
1
2
3

递归循环

[编辑 | 编辑源代码]
(defn my-zipmap [keys vals]
  (loop [my-map {}
         my-keys keys
         my-vals vals]
    (if (and (seq my-keys) (seq my-vals))
      (recur (assoc my-map (first my-keys) (first my-vals))
             (rest my-keys)
             (rest my-vals))
      my-map)))

(println (my-zipmap [:a :b :c] [1 2 3]))

{:c 3, :b 2, :a 1}

修改状态

[编辑 | 编辑源代码]

如何写 x = x+ 1

[编辑 | 编辑源代码]
user=> (def x (atom 0))
#'user/x
user=> (swap! x + 1)
1
user=> (swap! x + 1)
2
user=> @x
2

或者,更惯用的方法是使用 inc

user=> (def x (atom 0))
#'user/x
user=> (swap! x inc)
1
user=> (swap! x inc)
2
user=> @x
2

最惯用的方法是不这样做。为什么要在 clojure 中做 x=x+1 呢?

如何创建一个集合

[编辑 | 编辑源代码]
(def p #{1,2,3})

如何查找集合的并集/交集/差集

[编辑 | 编辑源代码]
(def a #{1,2,3,4})
(def b #{2,3,5})

user=> (clojure.set/union a b)
#{1 2 3 4 5}

user=> (clojure.set/intersection a b)
#{2 3}

user=> (clojure.set/difference a b)
#{1 4}

创建包含 n 个对象副本的列表

[编辑 | 编辑源代码]
user=> (repeat 10 "a")
("a" "a" "a" "a" "a" "a" "a" "a" "a" "a")

创建包含 n 个对无参数函数的调用的列表

[编辑 | 编辑源代码]
; repeatedly generates an infinite sequence of calls to a function that takes no arguments
; No calls are made until the result is needed! (do not try to evaluate this list directly, it is infinite, 
; and will run forever)
user=> (def random-ints (repeatedly #(rand-int 100)))

; use take to take the integers:
user=> (take 10 random-ints)
(66 8 31 90 78 18 28 8 94 3)

;NOTE: seqs are cached, so taking new random ints will always return the same result.
;This also means that if you take many ints from a global seq (def'd one) ALL the integers will
;stay in memory until the name is redefined to something else!

user=> (take 15 random-ints) ; first 10 are the same, 5 new ints generated
(66 8 31 90 78 18 28 8 94 3 84 29 71 85 41)

; to avoid a global list, you can do this:
(defn make-calls [n func]
  (take n (repeatedly func)))

; no fear of keeping huge lists in memory this time (unless you hold onto them, of course)
user=> (make-calls 5 #(rand-int 100))
(60 75 89 62 36)

; upon next call, the result will be different:
user=> (make-calls 5 #(rand-int 100))
(94 95 88 11 93)

将两个或多个序列追加或连接在一起

[编辑 | 编辑源代码]
user=> (concat [1 3] [3 4 3] [3 3])
(1 3 3 4 3 3 3)

无限序列

[编辑 | 编辑源代码]

生成一个无限循环列表

[编辑 | 编辑源代码]
(def x (cycle [1 2 3]))

user=> (take 12 x)
(1 2 3 1 2 3 1 2 3 1 2 3)

生成一个无限的随机数序列

[编辑 | 编辑源代码]
(def rands (repeatedly rand))

user=> (take 4 rands)
(0.39300911409554096 0.24329175257444235 0.03259576739916903 0.7459916914364135)
user=>

生成无限重复序列

[编辑 | 编辑源代码]
(def just4 (repeat 4))

user=> (take 5 just4)
(4 4 4 4 4)

生成无限嵌套函数调用的序列

[编辑 | 编辑源代码]

无限序列 (0 1 2 3 4 5 ....) 可以使用以下方式定义:(range)从 Clojure 1.2 开始:[1]

 (def integers (range))
 (take 10 integers)
 ;; ⇒ (0 1 2 3 4 5 6 7 8 9)

或者使用以下方式:iterate:

 ;; Generates (x (inc  x) (inc (inc  x)) ...)
 (def integers (iterate inc 0))
 (take 4 integers)
 ;; ⇒ (0 1 2 3)
 (def newton (iterate (fn[x] (/ (+ x (/ 2.0 x)) 2)) 2))
 (take 5 newton)
 ;; ⇒ (2 1.5 1.4166666666666665 1.4142156862745097 1.4142135623746899)

根据参数数量重载函数

(defn argcount
   ([] 0)                                ; Zero arguments
   ([x] 1)                               ; One argument
   ([x &  args] (+ 1 (count args))))    ; List of arguments

(argcount)
;; ⇒ 0
(argcount "dog")
;; ⇒ 1
(argcount "cat" 1 3 4)
;; ⇒ 4

创建根据参数数量进行分派的多分派方法

(defmulti g (fn[& arglist] (count arglist)))
(defmethod g 0 [& arglist] "No arguments.")
(defmethod g 1 [& arglist] "One argument.")
(defmethod g 2 [& arglist]  "Two arguments.")
(defmethod g :default [& arglist] "More than two arguments.")

(g)                    ; ⇒ "No arguments."
(g 1)                  ; ⇒ "One argument."
(g 2)                  ; ⇒ "One argument."
(g 3 4)                ; ⇒ "Two arguments."
(g "cart" 1 [2 3 ])    ; ⇒ "More than two arguments."

创建根据参数类进行分派的多分派方法

(comment Define f to be a multi-method function and dispatch using class of argument)
(defmulti f class)

(comment Use this definition for f if the class of the argument x is a long)
(defmethod f Long [x] "Argument is a long")

(comment Use this definition for f if the class of the argument x is a double)
(defmethod f Double [x] "Argument is a double")

(comment Use this definition for f for all other argument types )
(defmethod f :default [x] "Argument is not a number")

(f 3)               ; ⇒ "Argument is a long"
(f 3.4)             ; ⇒ "Argument is a double"
(f "string")        ; ⇒ "Argument is not a number"
(comment Define g to be a multi-method function which dispatches on the class of its arguments)
(defmulti g (fn[x,y] [(class x) (class y )]))

(comment Use this definition for g if class of first argument is a long and class of second argument is a long)
(defmethod g [Long,Long]  [x,y] "Both arguments are longs")

(comment Use this definition for g if class of first argument is long and class of second argument is double)
(defmethod g [Long,Double] [x,y] "First argument is a long and second argument is a double")

(comment Use this definition for g as the default )
(defmethod g :default [x,y] "All other cases")

(g 3 2)         ; ⇒ "Both arguments are longs"
(g 3 4.3)       ; ⇒ "First argument is a long and second argument is a double"
(g 4.3 4.3)     ; ⇒ "All other cases"

创建根据参数值进行分派的多分派方法

(comment Create multi method h that dispatches on the value of the argument)
(defmulti h (fn[x] x))

(comment Use this definition for h if argument is 4)
(defmethod h 4 [x] "argument is 4")

(comment Use this definition for other values of h)
(defmethod h :default [x] "argument is not 4")

(h 4)          ; ⇒ "argument is 4"
(h 3)          ; ⇒ "argument is not 4"
(h [3 34])     ; ⇒ "argument is not 4"

(comment Create multi method h that dispatches on the value of the argument being in an interval)
(defmulti h (fn [x] (<= 4 x 10)))

(comment Use this definition for h if argument is between 4 and 10 )
(defmethod h true [x] "argument is between 4 and 10")
    
(comment Use this definition for other values of h)
(defmethod h :default [x] "argument is not between 4 and 10")

(h 1)        ; ⇒ "argument is not between 4 and 10"
(h 4)        ; ⇒ "argument is between 4 and 10"
(h 10)       ; ⇒ "argument is between 4 and 10"
(h 11)       ; ⇒ "argument is not between 4 and 10"

实例化新的 Java 对象

[编辑 | 编辑源代码]
(new JFrame)

;; or
(JFrame.)

调用 Java 类的静态方法

[编辑 | 编辑源代码]
user=> (Math/cos 3)
-0.9899924966004454

user=> (. Math cos 3)
-0.9899924966004454

调用 Java 对象的非静态方法

[编辑 | 编辑源代码]
;;method name first
(.getContentPane frame)

;;object first
(. frame getContentPane)

访问内部类

[编辑 | 编辑源代码]

Java 中的类定义。

public class OkCancelDialog {
  //Inner class
  public static enum State {
    OK, CANCEL;
  };
}
;;accessing the inner class and its static fields
(println OkCancelDialog$State/OK)
(println OkCancelDialog$State/CANCEL)

嵌套的调用方法序列

[编辑 | 编辑源代码]
;; equivalent to frame.getContentPane().getSize()
(.. frame getContentPane getSize)

窗口中的简单绘图

[编辑 | 编辑源代码]
(ns drawing-demo
  (:import [javax.swing JPanel JFrame]
           [java.awt Dimension]))

(defn make-panel []
  (let [panel (proxy [JPanel] []
                (paintComponent [g]
                  (.drawLine g 0 0 100 100)))]
    (doto panel
      (.setPreferredSize (Dimension. 400 400)))))

(defn make-frame [panel]
  (doto (new JFrame)
    (.add panel)
    .pack
    .show))

(make-frame (make-panel))

从 Jar 文件导入 Java 类

[编辑 | 编辑源代码]
(import '(cljext.swing DelegatingPanel IPainter))

导入列表中的第一项是包名,后面跟着要导入的包中所有类的名称。

注意:您必须使用包名,而不是包含类包的 jar 文件名。如果您不确定包名是什么,请从终端输入

jar tf jarfilename.jar

假设您看到类似以下内容:org/jfugue/Anticipator.class

则导入语句将是

(import '(org.jfugue Anticipator))

而不是

(import '(jarfilename Anticipator))

文件 I/O

[编辑 | 编辑源代码]

加载文件 I/O 库

[编辑 | 编辑源代码]
(use 'clojure.contrib.duck-streams)

将文件的全部内容读入字符串

[编辑 | 编辑源代码]
(slurp "somefile.txt")

写入输出文件

[编辑 | 编辑源代码]

写入将创建一个新文件或覆盖现有文件

(spit "output.txt" "some output text")

要追加到现有文件,请使用“spit”,并将“:append”设置为 true

(spit "output.txt" "more text with spit append" :append true)

现在我们的文件应该显示

"some output textmore text with spit append"

字符串

[编辑 | 编辑源代码]

使用str连接字符串

(str "A" "B" "C")
;; ⇒ "ABC"

并使用apply以及str作为参数连接字符串序列

(apply str ["A" "B" "C"])
;; ⇒ "ABC"
(apply str ["/usr/include" \/ "stdio.h"])
;; ⇒ "/usr/include/stdio.h"

(map 
 (fn [file] (str "/usr/include/" file ".h"))
 ["stdio" "gmp" "signal"])
;; ⇒ ("/usr/include/stdio.h" "/usr/include/gmp.h" "/usr/include/signal.h")

使用interpose使用分隔符连接字符串序列

(apply str (interpose \: ["A" "B" "C"]))
;; ⇒ "A:B:C"

(apply str 
  (interpose \:
    ["/usr/local/sbin" "/usr/local/bin" "/usr/sbin" "/usr/bin" "/sbin" "/bin"]))
;; ⇒ "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

使用re-seq通过正则表达式在边界处拆分字符串,此处\w+表示所有字母数字字符以及“_”的 字符类

(re-seq #"\w+" "to be or not to be")
;; ⇒ ("to" "be" "or" "not" "to" "be")

反转字符串是通过以下方式完成的reverse它返回字符串中字符的序列;使用apply以及str将其再次转换为字符串

(reverse "I am cold")
;; ⇒ (\d \l \o \c \space \m \a \space \I)

(apply str (reverse "I am cold"))
;; ⇒ "dloc ma I"

以及将任何对象转换为字符串,只需将其作为参数提供给str函数

(str 3)             ; ⇒ "3"
(str 3.0)           ; ⇒ "3.0"
(str 'a)            ; ⇒ "a"
(str '(1 2))        ; ⇒ "(1 2)"
(str {:a 1 :b 2})   ; ⇒ "{:a 1, :b 2}"

操作系统调用

[编辑 | 编辑源代码]

获取当前工作目录

[编辑 | 编辑源代码]
user=> (System/getProperty "user.dir") 
"/Applications/clojure"

设置当前工作目录(结果是先前的工作目录)

[编辑 | 编辑源代码]
user=> (System/setProperty "user.dir" "~/test") 
"/Applications/clojure"

参考资料

[编辑 | 编辑源代码]
  1. range 在 ClojureDocs 上
华夏公益教科书