【问题标题】:Clojure in Action, Ch 12 Data Analysis example, dependency issuesClojure in Action,第 12 章数据分析示例,依赖问题
【发布时间】:2014-05-31 11:23:09
【问题描述】:

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

以下代码是分析依赖于 clojure.contrib 的文本的 map/reduce 方法。我尝试将 .split 函数更改为使用 #"\w+" 重新排序,使用 line-seq 而不是 read-lines,并将 .toLowerCase 更改为字符串/小写。我尝试将我的问题跟踪到源代码并彻底阅读文档以了解 read-lines 函数在您使用整个序列后关闭,并且 line-seq 返回一个惰性字符串序列,实现 java.io.BufferedReader。对我的问题最有帮助的是post about how to read files after 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) 在 average-line-length 命名空间中,在相同情况下我会遇到类似的名称冲突错误:

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

【问题讨论】:

  • 我猜您遇到的错误与 LightTable 有关。也许尝试在这个问题的标签中添加lighttable,看看是否有人观看该标签将能够提供一些指导?另外,尝试使用 Leiningen 从命令行运行您的代码,看看是否有什么不同。这些不是您在运行 Clojure 代码时通常会遇到的错误类型。
  • 为了记录,我使用 LightTable,但没有遇到这些问题,所以如果它是 LightTable 的东西,也许可以通过更改一些 LT 配置设置来修复它,或者通过从刮。不过,那是在黑暗中刺伤:)
  • 好点。我会在其他用途​​中尝试它,看看它是否继续发生,如果没有重新安装灯台。关于过时的习惯用法/依赖关系,有没有更好的方法来做到这一点?
  • 我得到了完全相同的错误,但作为终端 leiningen repl 和 emacs 中的警告。每个都提到 contrib.io 或 contrib.seq.utils,如果我更改代码,我会得到名称冲突错误,与上面相同。我很确定我需要换掉依赖项并使用不同的功能。但是哪些?
  • 额外信息:我很确定在 Clojure 1.2 中,您可以动态地将值绑定到符号,而无需声明它,并且按照惯例,它们是 earmuffed 的。在后来的版本中,这些约定变成了要求,遵循变异代码应该是强类型的理念。可以理解的是,一个 1.2 版的 contrib 库,它没有被维护以符合以后的要求,不会被更改以支持引擎盖下的动态绑定。此外,contrib 中使用的许多预定义符号被迁移到不同的命名空间,这意味着可能存在重叠。

标签: clojure mapreduce stack-trace dependency-management name-collision


【解决方案1】:

据我所知,clojure.contrib.ioclojure.contrib.seq-utils 已不再更新,实际上它们可能与 clojure.corespit 函数发生冲突。我建议去掉这些依赖项,看看你是否可以只使用核心功能来做到这一点。 spit 应该可以正常工作——你得到的错误是由 useing 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.ioclojure.contrib.seq-utils

【讨论】:

  • 这只是回答我的问题,但更简洁地说,我应该删除两个 contrib 库,将 (:import (java.io BufferedReader)) 放入 chapter-data.word-count-1 命名空间并直接从clojure.contrib.io 命名空间以满足我的需要。我做了所有这些,并且可以证明它有效。我只是很难相信 read-lines 还没有迁移到新的核心功能,也许有不同的名字。我看过 read-line、line-seq 等,但就是找不到我们的罪魁祸首!
  • 我认为line-seq 应该能够满足您的需求...是不是没有按预期工作?
  • 对源代码进行注解,line-seq 将 BufferedReader 的实例作为参数,然后返回一个惰性 seq,递归读取该阅读器中的每一行,而 read-lines 打开 BufferedReader 实例,获取file 作为参数,然后在关闭阅读器之前将每一行读入惰性序列。我不会撒谎,我真的不明白阅读器/缓冲区/或缓冲阅读器比这个单个示例更深入。这个问题涉及我们在这里处理的同一件事。 stackoverflow.com/questions/6613470/…
  • 这是关于 google clojure 组的类似主题的讨论:groups.google.com/forum/m/#!topic/clojure/MkHB3SHrGZY
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-22
  • 1970-01-01
  • 2011-04-24
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多