1

我正在读这本书的第一版,虽然我很喜欢它,但给出的一些例子似乎已经过时了。我会放弃并找到另一本书来学习,但我对作者所说的内容真的很感兴趣,并且想让这些例子适合我自己,所以我正在尝试更新它们。

以下代码是分析依赖于 clojure.contrib 的文本的 map/reduce 方法。我尝试将 .split 函数更改为使用 #"\w+" 重新排序,使用 line-seq 而不是 read-lines,并将 .toLowerCase 更改为字符串/小写。我尝试将我的问题跟踪到源代码并彻底阅读文档以了解 read-lines 函数在您使用整个序列后关闭,并且 line-seq 返回一个惰性字符串序列,实现 java.io.BufferedReader。对我的问题最有帮助的是关于如何在 clojure 1.3 之后读取文件的帖子。即使这样,我也无法让它工作。

所以这是我的问题:我需要在以下代码中更改哪些依赖项和/或函数以使其成为现代、可靠、惯用的 Clojure?

第一个命名空间:

(ns chapter-data.word-count-1
  (:use clojure.contrib.io
        clojure.contrib.seq-utils))

(defn parse-line [line]
  (let [tokens (.split (.toLowerCase line) " ")]
    (map #(vector % 1) tokens)))

(defn combine [mapped]
  (->> (apply concat mapped)
       (group-by first)
       (map (fn [[k v]]
              {k (map second v)}))
       (apply merge-with conj)))

(defn map-reduce [mapper reducer args-seq]
  (->> (map mapper args-seq)
       (combine)
       (reducer)))

(defn sum [[k v]]
  {k (apply + v)})

(defn reduce-parsed-lines [collected-values]
  (apply merge (map sum collected-values)))

(defn word-frequency [filename]
  (map-reduce parse-line reduce-parsed-lines (read-lines filename)))

第二个命名空间:

(ns chapter-data.average-line-length
  (:use rabbit-x.data-anal
        clojure.contrib.io))

(def IGNORE "_")

(defn parse-line [line]
  (let [tokens (.split (.toLowerCase line) " ")]
    [[IGNORE (count tokens)]]))

(defn average [numbers]
  (/ (apply + numbers)
     (count numbers)))

(defn reducer [combined]
  (average (val (first combined))))

(defn average-line-length [filename]
  (map-reduce parse-line reducer (read-lines filename)))

但是当我在 light table 中编译和运行它时,我得到了一堆错误:

1)在 word-count-1 命名空间中,当我在编辑后尝试重新加载 ns 函数时得到这个:

java.lang.IllegalStateException: spit already refers to: #'clojure.contrib.io/spit in namespace: chapter-data.word-count-1

2)在平均行长度命名空间中,在相同情况下,我会遇到类似的名称冲突错误:

clojure.lang.Compiler$CompilerException: java.lang.IllegalStateException: parse-line already refers to: #'chapter-data.word-count-1/parse-line in namespace: chapter-data.average-line-length, compiling:(/Users/.../average-line-length.clj:7:1)

3)奇怪的是,当我退出并重新启动 lig​​ht table 时,将代码直接复制并粘贴到文件中(替换那里的内容)并调用其顶级函数的实例 word-count-1 命名空间运行良好,给了我出现的次数test.txt 文件中的某些单词,但平均行长命名空间给了我这个:

"Warning: *default-encoding* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *default-encoding* or change the name. (clojure/contrib/io.clj:73)...

4)此时,当我调用word-frequency第一个命名空间的函数时,它返回nil而不是单词出现的次数,当我调用average-line-length第二个命名空间的函数时,它返回

java.lang.NullPointerException: null
            core.clj:1502 clojure.core/val
4

1 回答 1

2

据我所知,clojure.contrib.io并且clojure.contrib.seq-utils不再更新,实际上它们可能clojure.corespit. 我建议去掉这些依赖项,看看你是否可以只使用核心功能来做到这一点。spit应该可以正常工作-您遇到的错误是由useing引起的clojure.contrib.io,它包含自己的spit功能,看起来大致等效;也许当前版本clojure.coreclojure.contrib.io/spit.

您的parse-line函数问题似乎是由于您在两个不同的命名空间中定义了两个同名的函数。命名空间不相互依赖,但是如果你在一个 REPL 中加载两个命名空间,你仍然会遇到冲突。如果您一次只需要使用一个,请尝试使用其中一个,然后当您想使用另一个时,请确保(remove-ns name-of-first-ns)首先释放 var,以免发生冲突。或者,您可以parse-line在每个命名空间中创建一个私有函数,方法是更改(defn parse-line ...​​为(defn- parse-line ....

编辑:如果您仍然需要任何在或其他地方不可用或不可用的功能clojure.contrib.ioclojure.contrib.seq-utilscore始终可以将源代码复制到您的命名空间中。请参阅github 上的clojure.contrib.ioclojure.contrib.seq-utils 。

于 2014-04-17T14:52:31.507 回答