4

I'm having a problem stringing some forms together to do some ETL on a result set from a korma function.

I get back from 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"})

I'm looking to filter this result set by getting the "children" where the :a keyword is 1.

My attempt:

;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)))

What I'm expecting here is a vector of hashmaps that :a is set to 1, i.e :

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

However, I'm getting the following error:

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

From the error I gather it's trying to create a sequence from a function...though just not able to connect the dots as to why.

Further, if I separate the filter function entirely by doing the following:

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

it works. I'm not sure why the first-thread is not applying the filter function, passing in the :children vector as the coll argument.

Any and all help much appreciated.

Thanks

4

3 回答 3

6

你想要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))

这会导致错误,因为您的匿名函数不是序列。

于 2015-05-28T03:41:54.760 回答
3

thread-first ( ->) 和 thread-last ( ->>) 宏总是有问题的,因为在选择一个而不是另一个时很容易出错(或者像你在这里所做的那样混合它们)。分解步骤如下:

(ns tstclj.core
  (:use cooljure.core)  ; see https://github.com/cloojure/tupelo/
  (: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 tupelo.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})

这就是你想要的。问题是您确实需要对filter表单使用 thread-last。避免此问题的最可靠方法是始终明确并使用 Clojureas->线程形式,或者更好的是,it->来自Tupelo 库

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

通过使用线程优先,您意外地编写了与此等效的内容:

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

并且该错误反映了该函数#(= (% :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})

除非您的处理链非常简单,否则我发现使用it->表格来明确说明管道的每个阶段应如何使用中间值总是更清晰。

于 2015-05-28T04:06:29.277 回答
1

我不确定为什么第一个线程没有应用过滤器函数,将 :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}]
于 2015-05-28T03:44:11.243 回答