36r16 ,它们都是42进制。
尝试在匿名函数文字中返回字面量
这样做是可行的:
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}))
使用括号包裹的字面量地图。由于它是第一个元素,因此被视为函数(实际上是字面量地图),但没有提供必需的参数(如键)。总之,匿名函数文字不会扩展到不中
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
因此,你不能将任何字面值([], :a, 4, %)作为匿名函数的主体。
评论中提供了两种解决方案。 Brian Carper 建议使用序列实现构造函数(array-map、hash-set、vector),例如:
(#(array-map %1 %2) :a 1)
当 Dan 演示使用 identity 函数来取消外部括号时:
(#(identity {%1 %2}) :a 1)
Brian的建议实际上引出了我下一个错误...
认为hash-map或array-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 map的具体实现,但你应该知道像assoc或conj这样增加map的函数可以接受一个PersistentArrayMap并返回一个PersistentHashMap,对于更大的maps,后者的性能更快。
使用函数作为递归点而不是loop来提供初始绑定
当我开始编程时,我写了很多像这样的函数:
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
实际上,对于这个特定的函数来说,loop更加简洁和惯用。
(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))))
请注意,我用循环和初始绑定替换了空参数的“默认构造函数”函数体 (p3 775147 600851475143 3)。现在,recur重新绑定循环绑定变量(而不是fn参数),并跳回递归点(循环,而不是fn)。
引用“虚拟”变量
我在谈论您可能在REPL中定义的变量类型-在探索性编程期间-然后无意中在源代码中引用。一切都正常工作,直到您重新加载命名空间(可能通过关闭编辑器),然后发现整个代码中引用了大量未绑定的符号。这也经常发生在您重构时,将变量从一个命名空间移动到另一个命名空间。
将列表推导视为命令式for循环
基本上,您正在创建一个基于现有列表的惰性列表,而不是简单地执行受控循环。Clojure的doseq实际上更类似于命令式foreach循环结构。
它们不同的一个例子是能够使用任意谓词过滤它们迭代的元素:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
另一种不同之处在于它们可以操作无限惰性序列:
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")
同时也没有break或continue可以提前退出。
结构体的过度使用
我来自一种面向对象的背景,所以当我开始学习Clojure时,我的大脑仍然在思考对象。我发现自己将所有东西都建模为一个结构体,因为它的“成员”组合,无论多么松散,都让我感到舒适。实际上,结构体应该主要被视为一种优化;Clojure会共享键和一些查找信息以节省内存。您可以通过定义accessors来进一步优化它们,以加快键查找过程。
总的来说,与map相比,使用结构体并没有任何好处,除了性能,因此增加的复杂性可能不值得。
使用未经处理的BigDecimal构造函数
我需要很多BigDecimals,并且写了一些丑陋的代码,如下所示:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
实际上,Clojure通过在数字后添加M来支持BigDecimal字面量:
(= (BigDecimal. "42.42") 42.42M) ; true
使用精简版可以减少很多臃肿。在评论中,twils提到您还可以使用bigdec和bigint函数来更明确地表达,同时保持简洁。
使用Java包命名规则作为命名空间
这并不是一个错误,但它违反了典型Clojure项目的结构和命名惯例。我的第一个重要的Clojure项目有类似以下的命名空间声明 - 以及相应的文件夹结构:
(ns com.14clouds.myapp.repository)
这使得我的完全限定函数引用变得臃肿:
(com.14clouds.myapp.repository/load-by-name "foo")
为了让事情更加复杂,我使用了标准的Maven目录结构:
|
| |
| | |
| | |
| | |
| |
...
比“标准”的Clojure结构更复杂:
|
|
|
这是Leiningen项目和Clojure本身的默认设置。
地图使用 Java 的 equals() 而不是 Clojure 的 = 进行键匹配
最初由 IRC 上的 chouser 报告,使用 Java 的 equals() 导致一些令人费解的结果:
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
由于默认情况下,Integer和Long实例的1打印相同,因此很难检测为什么您的映射没有返回任何值。特别是当您通过一个函数传递键时,该函数可能会返回long,而您并不知道。
应注意,使用Java的equals()而不是Clojure的=对于映射符合java.util.Map接口至关重要。
我正在使用Stuart Halloway的Programming Clojure,Luke VanderHart的Practical Clojure以及无数Clojure黑客在IRC和邮件列表上的帮助来帮助我解决问题。
some
函数,或者更好的办法是直接使用contains
本身。Clojure集合实现了java.util.Collection
。(.contains [1 2 3] 2) => true
。 - Rayne