Clojure первая нить с функцией фильтра

У меня проблема с объединением нескольких форм для создания ETL в наборе результатов из функции korma.

Я вернусь из Korma sql:

({:id 1 :some_field "asd" :children [{:a 1 :b 2 :c 3} {:a 1 :b 3 :c 4} {:a 2 :b 2 :c 3}] :another_field "qwe"})

Я ищу, чтобы отфильтровать этот набор результатов, получив "детей", где :a Ключевое слово 1.

Моя попытка:

;mock of korma result
(def data '({:id 1 :some_field "asd" :children [{:a 1 :b 2 :c 3} {:a 1 :b 3 :c 4} {:a 2 :b 2 :c 3}] :another_field "qwe"}))

(-> data 
    first 
    :children 
    (filter #(= (% :a) 1)))

То, что я ожидаю здесь, это вектор хеш-карт, который:a установлен в 1, то есть:

[{:a 1 :b 2 :c 3} {:a 1 :b 3 :c 4}]

Однако я получаю следующую ошибку:

IllegalArgumentException Don't know how to create ISeq from: xxx.core$eval3145$fn__3146  clojure.lang.RT.seqFrom (RT.java:505)

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

Далее, если я полностью отделю функцию фильтра, выполнив следующее:

(let [children (-> data first :children)] 
    (filter #(= (% :a) 1) children))

оно работает. Я не уверен, почему первый поток не применяет функцию фильтра, передавая в :children вектор в качестве аргумента колл.

Любая помощь очень ценится.

Спасибо

3 ответа

Решение

Вы хотите thread-last макрос:

(->> data first :children (filter #(= (% :a) 1)))

доходность

({:a 1,:b 2,:c 3} {:a 1,:b 3,:c 4})

thread-first макрос в вашем исходном коде эквивалентен написанию:

(filter (:children (first data)) #(= (% :a) 1))

Что приводит к ошибке, потому что ваша анонимная функция не является последовательностью.

Макросы thread-first (->) и thread-last (->>) всегда проблематичны, так как легко ошибиться при выборе одного из них (или при их смешении, как вы сделали здесь). Разбейте шаги так:

(ns tstclj.core
  (:use cooljure.core)  ; see https://github.com/cloojure/cooljure
  (:gen-class))

(def data [ {:id 1 :some_field "asd" 
             :children [ {:a 1 :b 2 :c 3} 
                          {:a 1 :b 3 :c 4}
                          {:a 2 :b 2 :c 3} ] 
             :another_field "qwe"} ] )

(def v1    (first data))
(def v2    (:children v1))
(def v3    (filter #(= (% :a) 1) v2))

(spyx v1)    ; from cooljure.core/spyx
(spyx v2)
(spyx v3)

Вы получите результаты, такие как:

v1 => {:children [{:c 3, :b 2, :a 1} {:c 4, :b 3, :a 1} {:c 3, :b 2, :a 2}], :another_field "qwe", :id 1, :some_field "asd"}
v2 => [{:c 3, :b 2, :a 1} {:c 4, :b 3, :a 1} {:c 3, :b 2, :a 2}]
v3 => ({:c 3, :b 2, :a 1} {:c 4, :b 3, :a 1})

это то, что вы хотели. Проблема в том, что вам действительно нужно было использовать thread-last для формы (filter...). Самый надежный способ избежать этой проблемы - это всегда быть явным и использовать форму as-> threading:

(def result (as-> data it
                  (first it)
                  (:children  it)
                  (filter #(= (% :a) 1) it)))

Пока, используя thread-first, вы случайно написали эквивалент этого:

(def result (as-> data it
                  (first it)
                  (:children  it)
                  (filter it #(= (% :a) 1))))

и ошибка отражает тот факт, что FN #(= (% :a) 1) не может быть брошен в послед. Иногда стоит использовать форму let и дать имена промежуточным результатам:

(let [result-map        (first data)
      children-vec      (:children  result-map)
      a1-maps           (filter #(= (% :a) 1) children-vec) ]
  (spyx a1-maps))
;;-> a1-maps => ({:c 3, :b 2, :a 1} {:c 4, :b 3, :a 1})

Мы также могли бы взглянуть на любое из двух предыдущих решений и заметить, что выходные данные каждого этапа используются в качестве последнего аргумента для следующей функции в конвейере. Таким образом, мы могли бы также решить это с помощью thread-last:

(def result3  (->>  data
                    first
                    :children
                    (filter #(= (% :a) 1))))
(spyx result3)
;;-> result3 => ({:c 3, :b 2, :a 1} {:c 4, :b 3, :a 1})

Если ваша цепочка обработки не очень проста, я нахожу почти всегда более понятным использование формы as->, чтобы явно указать, как промежуточное значение должно использоваться на каждой стадии конвейера.

Я не уверен, почему первый поток не применяет функцию фильтра, передавая вектор: children в качестве аргумента coll.

Это именно то, что thread-first макрос делает.

С сайта clojuredocs.org:

Потоки expr через формы. Вставляет x как второй элемент в первую форму, создавая его список, если это еще не список.

Итак, в вашем случае применение filter заканчивается тем, что:

(filter [...] #(= (% :a) 1))

Если вы должны использовать thread-first (вместо thread-last), то вы можете обойти это, частично применяя filter и его предикат:

(->
  data
  first
  :children
  ((partial filter #(= (:a %) 1)))
  vec)

; [{:a 1, :b 2, :c 3} {:a 1, :b 3, :c 4}]
Другие вопросы по тегам