Fork me on GitHub
#specter
<
2017-11-25
>
benstox16:11:51

Hello, a very beginner question here. Say I have a data structure like this and I want to select all the foods that have 3 pieces of cheese in them:

(def kitchen {:foods [{:type :sandwich :ingredients [{:type :bread :quantity 3}
                                                     {:type :cheese :quantity 2}
                                                     {:type :meatball :quantity 5}]}
                      {:type :sandwich :ingredients [{:type :bread :quantity 2}
                                                     {:type :cheese :quantity 3}
                                                     {:type :ham :quantity 2}]}
                      {:type :quiche :ingredients [{:type :egg :quantity 5}
                                                   {:type :cheese :quantity 5}]}]})
That is, I expect to return the ham sandwich and the quiche. I understand how I could use Specter to navigate to all the quantities of cheese that are greater than 3:
(select [:foods ALL :ingredients ALL #(= (:type %) :cheese) :quantity #(>= % 3)] kitchen)
but what I really want to do is this:
(defn three-cheese-food? [food]
  (->> food
    (:ingredients)
    (some
      (fn [ingredient]
        (and
          (= (:type ingredient) :cheese)
          (>= (:quantity ingredient) 3))))))

(filter three-cheese-food? (:foods kitchen))
Can I use Specter to do the work done in my filtering and three-cheese-food? function? Thanks!

nathanmarz17:11:50

@benstox yes, use selected?

nathanmarz17:11:54

(select [:foods
         ALL
         (selected? :ingredients ALL #(= :cheese (:type %)) :quantity (pred>= 3))]
  kitchen)

benstox17:11:56

Ah, selected?! That was the piece I was missing. Thanks!

benstox17:11:23

And the pred>= too.

benstox19:11:30

Is there a Specter-y way to add multiple conditions to the filtering? I thought of this:

(select [:foods
         ALL
         (selected? :ingredients ALL #(= :cheese (:type %)) (multi-path [:quantity (pred>= 3)]
                                                                        [:quality (pred>= 10)]))])
But this seems to act like an OR when what I want is an AND.

swizzard19:11:22

i'm having a tough time requiring specter in a lumo repl:

swizzard19:11:24

cljs.user=> (require '[com.rpl.specter :as s :refer-macros [select transform]])
Could not require com.rpl.specter.navs in file com/rpl/specter.cljc
Could not require com.rpl.specter in file com/rpl/specter/navs.cljc
         (new)
         Function.cljs.core.ex_info.cljs$core$IFn$_invoke$arity$3 (NO_SOURCE_FILE <embedded>:1928:72)
         Function.cljs.analyzer.error.cljs$core$IFn$_invoke$arity$3 (NO_SOURCE_FILE <embedded>:2539:92)
         Function.cljs.js.require.cljs$core$IFn$_invoke$arity$5 (NO_SOURCE_FILE <embedded>:5849:145)
         Object.cljs.js.load_macros (NO_SOURCE_FILE <embedded>:5883:41)
         (NO_SOURCE_FILE <embedded>:5883:374)
         Function.cljs.js.require.cljs$core$IFn$_invoke$arity$5 (NO_SOURCE_FILE <embedded>:5838:1)
         Object.cljs.js.load_macros (NO_SOURCE_FILE <embedded>:5883:41)
         (NO_SOURCE_FILE <embedded>:5905:316)
         (NO_SOURCE_FILE <embedded>:5918:218)

Invalid regular expression: /\.js$/: Stack overflow
         String.replace (NO_SOURCE_FILE <anonymous>)
         Object.clojure.string.replace_all (NO_SOURCE_FILE <embedded>:1959:385)
         Object.clojure.string.replace (NO_SOURCE_FILE <embedded>:1962:191)
         (Object.cljs$stacktrace$remove_ext)
         (Object.cljs$stacktrace$mapped_line_column_call)
         (Object.cljs$stacktrace$mapped_frame)
         Function.<anonymous> (evalmachine.<anonymous>:512:24)
         Function.cljs.core.apply_to_simple.cljs$core$IFn$_invoke$arity$3 (NO_SOURCE_FILE <embedded>:771:157)
         Function.cljs.core.apply_to_simple.cljs$core$IFn$_invoke$arity$2 (NO_SOURCE_FILE <embedded>:770:188)
         Function.cljs.core.apply.cljs$core$IFn$_invoke$arity$2 (NO_SOURCE_FILE <embedded>:783:244)

nathanmarz20:11:37

@benstox selected? is true if it navigates to at least one value, so multi-path in a selected? acts like an or

nathanmarz20:11:18

for multiple conditions just use multiple selected? or multiple predicates in the path, e.g. [#(...) #(...) (selected? ...)]

nathanmarz20:11:12

@swizzard don't know anything about lumo, you'll probably have better luck in #clojurescript

swizzard20:11:24

fair enough, thanks

drowsy20:11:02

@benstox I wrote a small lib to tackle such a selection more declarative: https://github.com/IamDrowsy/ebenbild

drowsy20:11:22

in your case (like {:type :cheese :quantity #(>= % 3) :quality #(>= % 10)}) would work

drowsy20:11:08

it returns a predicate that can be used directly in a specter path

benstox22:11:33

@nathanmarz Okay, makes sense. Thanks! @drowsy Interesting! I’ll try your library out.