Clojure в действии, пример анализа данных, глава 12, проблемы с зависимостями

Я работаю над первым изданием этой книги, и хотя мне это нравится, некоторые из приведенных примеров устарели. Я бы сдался и нашел бы другую книгу для изучения, но мне действительно интересно, о чем говорит автор, и я хочу, чтобы примеры работали для меня, поэтому я стараюсь обновлять их по мере продвижения.

Следующий код представляет собой подход "карта / сокращение" к анализу текста, который зависит от clojure.contrib. Я попытался изменить функцию.split на re-seq с помощью #"\w+", использовал line-seq вместо read-lines и изменил.toLowerCase на string/ нижний регистр. Я попытался проследить свои проблемы до исходного кода и тщательно прочитать документы, чтобы узнать, что функция 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)))

Но когда я компилирую и запускаю его в легкой таблице, я получаю массу ошибок:

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) Как ни странно, когда я выхожу и перезапускаю легкую таблицу, копирую и вставляю код прямо в файлы (заменяя то, что там есть) и вызываю экземпляры своих функций верхнего уровня, пространство имен 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

1 ответ

Решение

Насколько я могу сказать, clojure.contrib.io а также clojure.contrib.seq-utils больше не обновляются, и на самом деле они могут конфликтовать с clojure.core функции как spit, Я бы порекомендовал убрать эти зависимости и посмотреть, сможете ли вы сделать это, используя только основные функции. spit должен просто работать - ошибка, которую вы получаете, вызвана useИНГ clojure.contrib.io, который содержит свой собственный spit функция, которая выглядит примерно эквивалентной; возможно текущая версия в clojure.core это "новая и улучшенная" версия clojure.contrib.io/spit,

Ваша проблема с parse-line Похоже, что функция вызвана тем фактом, что вы определили две функции с одинаковым именем в двух разных пространствах имен. Пространства имен не зависят друг от друга, но вы все равно можете столкнуться с конфликтом, если загрузите оба пространства имен в REPL. Если вам нужно использовать только один, попробуйте использовать один из них, а затем, когда вы хотите использовать другой, убедитесь, что вы делаете (remove-ns name-of-first-ns) сначала освободить вары, чтобы не было конфликта. В качестве альтернативы, вы можете сделать parse-line частная функция в каждом пространстве имен, путем изменения (defn parse-line ... в (defn- parse-line ...,

РЕДАКТИРОВАТЬ: Если вам все еще нужны какие-либо функции, которые были в clojure.contrib.io или же clojure.contrib.seq-utils которые не доступны в core или в другом месте, вы всегда можете скопировать источник в ваше пространство имен. Смотрите clojure.contrib.io и clojure.contrib.seq-utils на github.

Другие вопросы по тегам