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}]