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

时间:2010-01-07 13:27:54

标签: clojure

Clojure开发人员犯了哪些常见错误,我们如何避免这些错误?

例如; Clojure的新人认为contains?函数与java.util.Collection#contains的作用相同。但是,contains?仅在与索引集合(如地图和集合)一起使用时才会起作用,并且您正在寻找给定的密钥:

(contains? {:a 1 :b 2} :b)
;=> true
(contains? {:a 1 :b 2} 2)
;=> false
(contains? #{:a 1 :b 2} :b)
;=> true

与数字索引集合(向量,数组)一起使用时contains? 检查给定元素是否在有效索引范围内(从零开始):

(contains? [1 2 3 4] 4)
;=> false
(contains? [1 2 3 4] 0)
;=> true

如果给出一个列表,contains?永远不会返回true。

8 个答案:

答案 0 :(得分:71)

答案 1 :(得分:42)

忘记强制评估懒惰的seqs

除非您要求对它们进行评估,否则不会评估延迟seqs。你可能希望这会印刷一些东西,但事实并非如此。

user=> (defn foo [] (map println [:foo :bar]) nil)
#'user/foo
user=> (foo)
nil

永远不会评估map,它会被默默地丢弃,因为它很懒惰。您必须使用doseqdorundoall等中的一个来强制评估延迟序列的副作用。

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

在REPL上使用裸map看起来有效,但它只能起作用,因为REPL会强制评估lazy seqs本身。这可能会使bug更难以注意到,因为您的代码在REPL中工作,并且不能从源文件或函数内部工作。

user=> (map println [:foo :bar])
(:foo
:bar
nil nil)

答案 2 :(得分:21)

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

试图打印无限的懒惰序列。

我知道我在懒惰的序列中做了什么,但出于调试目的,我插入了一些print / prn / pr调用,暂时忘记了我正在打印的内容。好笑,为什么我的电脑都挂了?

试图强制编写Clojure。

有一些诱惑要创建大量refatom s并编写代码,这些代码会不断地与其状态混淆。这可以做到,但它不适合。它的性能可能也很差,并且很少受益于多个内核。

尝试在功能上100%编写Clojure。

另一方面:有些算法确实需要一些可变状态。不惜一切代价宗教地避免可变状态可能导致算法缓慢或笨拙。做出决定需要判断力和一点经验。

试图在Java中做太多。

因为它很容易与Java接触,所以有时候很容易使用Clojure作为Java的脚本语言包装器。当然,在使用Java库功能时你需要做到这一点,但是(例如)在Java中维护数据结构,或使用Java数据类型(如Clojure中具有良好等价物的集合)几乎没有意义。

答案 3 :(得分:13)

保持头脑循环。
如果在保留对第一个元素的引用的同时循环遍历可能非常大或无限的延迟序列的元素,则可能会耗尽内存。

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

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

在参考文献中加入可变的内容。
您可以在技术上这样做,但只有ref本身中的对象引用由STM控制 - 而不是引用的对象及其字段(除非它们是不可变的并指向其他引用)。所以只要有可能,就更喜欢refs中只有不可变的对象。原子也一样。

答案 4 :(得分:13)

已经提到了很多东西。我再添加一个。

Clojure if 将Java Boolean对象始终视为true,即使它的值为false。因此,如果您有一个返回java布尔值的java land函数,请确保不要直接检查它  (if java-bool "Yes" "No") 反而 (if (boolean java-bool) "Yes" "No")

我被clojure.contrib.sql库烧毁了,它将数据库布尔字段作为java布尔对象返回。

答案 5 :(得分:9)

使用loop ... recur处理地图时的序列。

(defn work [data]
    (do-stuff (first data))
    (recur (rest data)))

VS

(map do-stuff data)

map函数(在最新的分支中)使用分块序列和许多其他优化。此外,由于此功能经常运行,Hotspot JIT通常会对其进行优化,并准备好进行任何“预热时间”。

答案 6 :(得分:5)

集合类型对某些操作有不同的行为:

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"

答案 7 :(得分:3)

太多的parantheses,尤其是内部的void java方法调用会导致NPE:

public void foo() {}

((.foo))

导致外部parantheses的NPE,因为内部parantheses评估为nil。

public int bar() { return 5; }

((.bar)) 

导致更容易调试:

java.lang.Integer cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]