文字八进制
有一次,我正在读一个矩阵,该矩阵使用前导零来维持适当的行和列。从数学上讲这是正确的,因为前导零显然不会改变基础价值。尝试使用此矩阵定义var时,将神秘地失败:
java.lang.NumberFormatException: Invalid number: 08
这完全让我感到困惑。原因是Clojure将前导零的文字整数值视为八进制,而八进制中没有数字08。
我还应该提到Clojure通过0x前缀支持传统的Java十六进制值。您还可以通过使用“ base + r + value”表示法使用介于2到36之间的任何底数,例如2r101010或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 %1 %2) :a 1)
而丹表明,你可以使用身份功能拆开包装,外层的括号:
(#(identity {%1 %2}) :a 1)
布莱恩的建议实际上使我陷入下一个错误。
认为哈希映射或数组映射确定不变的具体映射实现
考虑以下:
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映射的具体实现,但是您应该知道,增长映射的函数(例如assoc或conj)可以采用PersistentArrayMap并返回PersistentHashMap,对于较大的映射,该函数执行得更快。
使用函数作为递归点而不是循环来提供初始绑定
当我开始时,我写了很多这样的函数:
; 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))))
实际上,对于此特定功能,循环会更加简洁明了:
; 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))))
请注意,我用循环+初始绑定替换了空参数“默认构造函数”函数体(p3 775147 600851475143 3)。在易复发,现在重新绑定环路绑定(而不是FN参数)和跳跃(而不是FN环)回递归点。
引用“幻像”变量
我说的是您可能在探索性编程期间使用REPL定义的var类型,然后在不知不觉中在您的源代码中引用。一切工作正常,直到您重新加载命名空间(也许通过关闭编辑器),然后发现整个代码中引用的一堆未绑定符号。当您进行重构时,将var从一个名称空间移动到另一个名称空间时,也会经常发生这种情况。
将for 列表理解视为必须的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")
也没有任何中断或继续过早退出。
过度使用结构
我来自OOPish背景,因此当我开始使用Clojure时,我的大脑仍在思考物体。我发现自己将所有事物都建模为结构,因为它的“成员”分组无论多么松散,都让我感到很舒服。实际上,大多数情况下应将结构视为优化。Clojure将共享密钥和一些查找信息以节省内存。您可以通过定义访问器来进一步优化它们,以加快密钥查找过程。
总体而言,除了性能之外,在映射上使用结构不会获得任何好处,因此增加的复杂性可能不值得。
使用不敏感的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目录结构:
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
它比以下“标准” Clojure结构更复杂:
|-- src/
|-- test/
|-- resources/
这是Leiningen项目和Clojure本身的默认设置。
映射使用Java的equals()而不是Clojure的=进行键匹配
最初由chouser在IRC上报道,这种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均打印为相同,因此很难检测为什么地图不返回任何值。当您通过可能不为人所知的函数传递键而返回键时,尤其如此。
应该注意的是,使用Java的equals()代替Clojure的=对映射符合java.util.Map接口至关重要。
我正在使用Stuart Halloway 编写的Programming Clojure,Luke VanderHart编写的实用Clojure,以及IRC和邮件列表上无数Clojure黑客的帮助,以帮助我解答。