Clojure开发人员要避免的常见编程错误

Clojure开发人员要避免的常见编程错误,clojure,Clojure,Clojure开发人员常见的错误有哪些?我们如何避免这些错误 比如,;Clojure的新手认为contains?函数的工作原理与java.util.Collection#contains相同。但是,包含?仅在与索引集合(如地图和集合)一起使用并且您正在查找给定的键时,才会起到类似的作用: (contains? {:a 1 :b 2} :b) ;=> true (contains? {:a 1 :b 2} 2) ;=> false (contains? #{:a 1 :b 2} :b)

Clojure开发人员常见的错误有哪些?我们如何避免这些错误

比如,;Clojure的新手认为
contains?
函数的工作原理与
java.util.Collection#contains
相同。但是,
包含?
仅在与索引集合(如地图和集合)一起使用并且您正在查找给定的键时,才会起到类似的作用:

(contains? {:a 1 :b 2} :b)
;=> true
(contains? {:a 1 :b 2} 2)
;=> false
(contains? #{:a 1 :b 2} :b)
;=> true
与数字索引集合(向量、数组)一起使用时,
包含?
仅检查给定元素是否在有效的索引范围内(从零开始):


如果给定一个列表,
包含?
将永远不会返回true。

我是一个Clojure noob。更高级的用户可能会遇到更有趣的问题

试图打印无限的惰性序列。 我知道我在用我的惰性序列做什么,但出于调试目的,我插入了一些print/prn/pr调用,暂时忘记了我正在打印什么。有趣的是,为什么我的电脑都挂断了

试图强制编程Clojure。 创建大量的
ref
s或
atom
s并编写不断破坏其状态的代码是有诱惑力的。这是可以做到的,但不是很合适。它的性能也可能很差,而且很少从多核中受益

试图对Clojure进行100%功能性编程。 另一方面:有些算法确实需要一点可变状态。不惜一切代价避免可变状态可能会导致算法缓慢或笨拙。做决定需要判断和一点经验

试图用Java做太多的事情。
因为接触Java非常容易,所以有时很容易将Clojure用作Java的脚本语言包装器。当然,在使用Java库功能时,您需要完全做到这一点,但是(例如)在Java中维护数据结构,或者使用Java数据类型(例如Clojure中有很好的等价物的集合)没有什么意义。

让您的头脑处于循环中。
如果在保留对第一个元素的引用的同时循环一个可能非常大或无限长的惰性序列的元素,则可能会出现内存不足的风险

忘记没有TCO。
常规尾部调用会占用堆栈空间,如果不小心,它们会溢出。Clojure有
'recur
'trampoline
来处理许多在其他语言中使用优化尾部调用的情况,但这些技术必须有意应用

不太懒。
您可以使用
'lazy-seq
'lazy-cons
(或通过构建更高级别的lazy API)构建一个惰性序列,但如果您将其包装在
'vec
中或通过实现该序列的其他函数传递,则它将不再是惰性的。堆栈和堆都可能因此溢出

将可变事物放入参考中。

您可以从技术上做到这一点,但只有ref本身中的对象引用受STM控制,而不是引用的对象及其字段(除非它们不可变并指向其他ref)。因此,只要有可能,最好只选择引用中的不可变对象。原子也是如此。

字面八进制

有一次我在读一个矩阵,它使用前导零来保持正确的行和列。从数学上讲,这是正确的,因为前导零显然不会改变基础值。然而,尝试使用此矩阵定义var将在以下情况下神秘地失败:

java.lang.NumberFormatException: Invalid number: 08
这完全让我困惑。原因是Clojure将带前导零的文字整数值视为八进制,八进制中没有数字08

我还应该提到Clojure通过0x前缀支持传统的Java十六进制值。您还可以使用“base+r+value”表示法使用2到36之间的任何基数,例如2R101036r16,它们是以42为基数的10


尝试以文本形式返回文本

这项工作:

user> (defn foo [key val]
    {key val})
#'user/foo
user> (foo :a 1)
{:a 1}
所以我相信这也会起作用:

(#({%1 %2}) :a 1)
但它在以下方面失败了:

java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
因为#()读卡器宏被扩展到

(fn [%1 %2] ({%1 %2}))  
将映射文字包装在括号中。因为它是第一个元素,所以它被视为一个函数(实际上是一个文本映射),但没有提供必需的参数(例如键)。总之,匿名函数literal不扩展为

(fn [%1 %2] {%1 %2})  ; notice the lack of parenthesis
因此,您不能将任何文本值([],:a,4,%)作为匿名函数的主体

user=> (map println [:foo :bar])
(:foo
:bar
nil nil)
评论中给出了两种解决方案Brian Carper建议使用序列实现构造函数(数组映射、哈希集、向量),如下所示:

Dan显示您可以使用函数展开外括号:

(#(identity {%1 %2}) :a 1)
布莱恩的建议实际上把我带到了下一个错误


认为或决定不变的具体map实施

考虑以下几点:

user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
虽然您通常不必担心Clojure映射的具体实现,但您应该知道,生成映射的函数(如assocconj)可以获取PersistentArrayMap并返回PersistentHeashMap,这对于较大的映射执行得更快


使用函数作为递归点,而不是函数来提供初始绑定

刚开始时,我写了很多类似这样的函数:

; Project Euler #3
(defn p3 
  ([] (p3 775147 600851475143 3))
  ([i n times]
    (if (and (divides? i n) (fast-prime? i times)) i
      (recur (dec i) n times))))
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
(ns com.14clouds.myapp.repository)
事实上,对于这个特定的功能来说,更简洁、更习惯:

; Elapsed time: 387 msecs
(defn p3 [] {:post [(= % 6857)]}
  (loop [i 775147 n 600851475143 times 3]
    (if (and (divides? i n) (fast-prime? i times)) i
      (recur (dec i) n times))))
注意,我用循环+初始绑定替换了空参数“default constructor”函数体(p3 775147 600851475143 3)重复现在重新绑定循环绑定(而不是fn参数)
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
(= (BigDecimal. "42.42") 42.42M) ; true
(ns com.14clouds.myapp.repository)
(com.14clouds.myapp.repository/load-by-name "foo")
|-- src/
|   |-- main/
|   |   |-- java/
|   |   |-- clojure/
|   |   |-- resources/
|   |-- test/
...
|-- src/
|-- test/
|-- resources/
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
(defn work [data]
    (do-stuff (first data))
    (recur (rest data)))
(map do-stuff data)
user=> (defn foo [] (map println [:foo :bar]) nil)
#'user/foo
user=> (foo)
nil
user=> (defn foo [] (doseq [x [:foo :bar]] (println x)) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil
user=> (defn foo [] (dorun (map println [:foo :bar])) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil
user=> (map println [:foo :bar])
(:foo
:bar
nil nil)
public void foo() {}

((.foo))
public int bar() { return 5; }

((.bar)) 
java.lang.Integer cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]
user=> (conj '(1 2 3) 4)    
(4 1 2 3)                 ;; new element at the front
user=> (conj [1 2 3] 4) 
[1 2 3 4]                 ;; new element at the back

user=> (into '(3 4) (list 5 6 7))
(7 6 5 3 4)
user=> (into [3 4] (list 5 6 7)) 
[3 4 5 6 7]
user=> (filter #(> (int %) 96) "abcdABCDefghEFGH")
(\a \b \c \d \e \f \g \h)
user=> (apply str (filter #(> (int %) 96) "abcdABCDefghEFGH"))
"abcdefgh"