1:不recur
。很快。对于您调用的每个函数,VM 都会产生一些开销和“噪音”:例如,REPL 需要解析和评估您的调用,否则可能会发生一些垃圾收集。这就是为什么对如此微小的代码进行基准测试没有任何意义。
与之比较:
(defn
count-leading-hash
[line]
(let [cnt (count (take-while #(= % \#) line))]
(if (> cnt 6) 6 cnt)))
(defn
count-leading-hash2
[line]
(loop [cnt 0]
(if (and (= (.charAt line cnt) \#) (< cnt 6))
(recur (inc cnt))
cnt)))
(def lines ["### Line one" "######## Line two"])
(time (dorun (repeatedly 10000 #(dorun (map count-leading-hash lines)))))
;; "Elapsed time: 620.628 msecs"
;; => nil
(time (dorun (repeatedly 10000 #(dorun (map count-leading-hash2 lines)))))
;; "Elapsed time: 592.721 msecs"
;; => nil
没有显着差异。
2:在这种情况下使用loop
/recur
不是惯用的;最好仅在真正需要时使用它,并在可能的情况下使用其他可用功能。有许多对集合/序列进行操作的有用函数;检查ClojureDocs以获取参考和示例。根据我的经验,对函数式编程不熟悉的具有命令式编程技能的人比那些有大量 Clojure 经验的人使用loop
/更多;/可能是代码气味。recur
loop
recur
3:我更喜欢第一个版本。有很多不同的方法:
;; more expensive, because it iterates n times, where n is the number of #'s
(defn count-leading-hash [line]
(min 6 (count (take-while #(= \# %) line))))
;; takes only at most 6 characters from line, so less expensive
(defn count-leading-hash [line]
(count (take-while #(= \# %) (take 6 line))))
;; instead of an anonymous function, you can use `partial`
(defn count-leading-hash [line]
(count (take-while (partial = \#) (take 6 line))))
编辑:
如何决定何时使用partial
与匿名函数?
就性能而言,这并不重要,因为(partial = \#)
评估为(fn [& args] (apply = \# args))
. #(= \# %)
转换为(fn [arg] (= \# arg))
。两者非常相似,但partial
为您提供了一个接受任意数量参数的函数,因此在您需要它的情况下,这就是要走的路。partial
是lambda 演算中的 λ (lambda) 。我想说,使用更容易阅读的东西,或者partial
如果你需要一个带有任意数量参数的函数。