Ошибки Clojure clojure.lang.LazySeq

Кажется, у меня неприятная проблема с вводом-выводом clojure (или системой типов). Дело в том, что эта функция, которая, как я ожидаю, будет потреблять набор коллекций строк и чисел или строк, и возвращает словарь строк, связанных с числами, например

(costlist '( '("Milk" 4) '("Bread" 2) '("Milk")))

давать

{"Milk" 4, "Bread" 2 }

Определяется

(defn costlist [lst]
    ;returns list of costs and appropriate names
          (let  [snds (filter (fn [x] (not (identical? nil x))) (seconds lst))]
          (zipmap 
                   (firsts (take (count snds) (firsts lst))) 
                   (snds  lst))))

при использовании списков типа clojure.lang.PersistentList (который я преобразовал из clojure.lang.LazySeq) выдает сообщение об ошибке

clojure.lang.LazySeq cannot be cast to clojure.lang.IFn

Что только смущает меня, так как ни один из его аргументов не кажется мне LazySeq.


person Jakub Bartczuk    schedule 06.08.2013    source источник


Ответы (3)


Проблема в том, что snds — это ленивая последовательность, поэтому (snds lst) выдает ошибку. filter всегда возвращает ленивую последовательность.

Ваша функция также слишком сложна. Попробуйте сделать проще:

(defn costlist [lst]
  (zipmap (map first lst)
          (remove nil? (map second lst))))

Теперь вы можете делать то, что хотите:

(costlist (list '("Milk" 4) '("Bread" 2) '("Milk")))

Я использую list, потому что quote предотвращает вычисление выражения (см. ответ ToBeReplaced):

=> '( '("Milk" 4) '("Bread" 2) '("Milk"))
((quote ("Milk" 4)) (quote ("Bread" 2)) (quote ("Milk")))

Таким образом, вам следует избегать использования quote для создания списков.

Ваше решение также предполагает, что значения nil могут встречаться только в конце списка. Вы можете легко это исправить:

(defn costlist [lst]
  (->>  (filter (comp #{2} count) lst)
        (map vec)
        (into {})))

А сейчас

(costlist (list '("Milk" 4) '("Milk") '("Bread" 2)))

тоже будет работать.

person Leonid Beschastny    schedule 06.08.2013

Трудно дать вам точный ответ, потому что вы также используете некоторые свои собственные функции (секунды и первые). Однако вам следует подумать, хотите ли вы, чтобы costlist была приведенной выше формой в кавычках.

Вышеприведенное эквивалентно:

((quote ("Milk" 4)) (quote ("Bread" 2)) (quote ("Milk")))

Я думаю, вы хотите что-то большее, например:

(list '("Milk" 4) '("Bread" 2) '("Milk"))

В вашем примере самая внешняя цитата вызывает цитирование внутренних кавычек!

person ToBeReplaced    schedule 06.08.2013

Не используйте ' внутри '(). Вы не хотите quote ' получить quote. Учитывая, что вы пишете свой список как:

'(("Milk" 4)("Bread" 2)("Milk"))

Самым элегантным, вероятно, будет:

(def costlist (comp (partial into {}) 
                    (partial map vec) 
                    (partial filter second))

похожий на

(def costlist #(into {} (map vec (filter second %))))

К сожалению, это создаст две ленивые последовательности, и одна будет лучше для производительности. Вы могли бы (use '(clojure.core [reducers :as r]) и составить себе крутую редукционную функцию:

(def r-fn (comp (r/map vec) (r/filter second)))

Что оставляет вас

(def costlist #(into {} (r-fn %)))

Благодаря into использованию reduce conj элементы вашего списка будут выделены только один раз.

Поскольку порядок для хэш-карт не имеет значения, вы также можете использовать r/fold. Затем ваш список будет отфильтрован, а элементы преобразованы в векторы параллельно (если в вашем списке более 512 элементов).

(def constlist #(r/fold (r/monoid conj hash-map) (r-fn %)))

В зависимости от размера коллекции редукторы могут быть слишком тяжелыми. Для небольшого размера, как в вашем примере, я рекомендую написать собственную функцию сокращения:

(def costlist #(reduce (fn [acc [k v]]
                         (if v
                           (assoc acc k v)
                           acc)) {} %)
person Leon Grapenthin    schedule 06.08.2013