Clojure in Action,第 12 章数据分析示例,依赖关系问题



我正在阅读本书的第一版,虽然我喜欢它,但给出的一些例子似乎已经过时了。我会放弃并找到另一本书来学习,但我对作者在谈论的内容非常感兴趣,并希望让这些示例为自己工作,所以我正在尝试在进行过程中更新它们。

以下代码是一种用于分析依赖于 clojure.contrib 的文本的 map/reduce 方法。我尝试将 .split 函数更改为带有 #"\w+" 的 re-seq,使用 line-seq 而不是 read-lines,并将 .toLowerCase 更改为字符串/小写。我试图将我的问题追溯到源代码并彻底阅读文档,以了解读取行函数在您使用整个序列后关闭,并且 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)))

但是当我在轻量表中编译并运行它时,我收到一系列错误:

1

(在字数统计-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(奇怪的是,当我退出并重新启动轻量级表时,将代码直接复制并粘贴到文件中(替换那里的内容(并调用其顶级函数的实例,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

我所知,clojure.contrib.ioclojure.contrib.seq-utils不再更新,实际上它们可能与spitclojure.core函数冲突。我建议删除这些依赖项,看看是否可以仅使用核心函数来执行此操作。 spit应该可以正常工作 - 您得到的错误是由use clojure.contrib.io引起的,它包含自己的spit函数,看起来大致相同;也许clojure.core的当前版本是clojure.contrib.io/spit的"新的和改进的"版本。

parse-line函数的问题似乎是由于您在两个不同的命名空间中定义了两个具有相同名称的函数而导致的。命名空间不相互依赖,但如果在 REPL 中加载两个命名空间,仍可能遇到冲突。如果一次只需要使用一个,请尝试使用其中一个,然后当您想使用另一个时,请确保先执行(remove-ns name-of-first-ns)以释放变量,以免发生冲突。或者,您可以通过将(defn parse-line ...更改为 (defn- parse-line ... 来使parse-line成为每个命名空间中的私有函数。

编辑:如果您仍然需要clojure.contrib.ioclojure.contrib.seq-utils中在core或其他地方不可用的任何函数,您可以随时将源代码复制到命名空间中。请参阅 github 上的 clojure.contrib.io 和 clojure.contrib.seq-utils。

最新更新