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.