Fork me on GitHub
#core-logic
<
2019-11-16
>
xiongtx00:11:11

I’m trying to find a more natural way of destructuring maps. Basically, I want:

(require '[clojure.core.logic :as l)

(l/run* [q b d]
  (l/featurec q {:a {:b b}
                 :d d})
<some other constraints>
  (l/== q {:a {:b 1
               :c "irrelevant"}
           :d [1 2 3]
           :e "whatever"}))
To return ([{:a {:b 1}, :d [1 2 3]} 1 [1 2 3]]), i.e. q should have only the form specified in the featurec constraint. What’s the best way to do that?

noprompt01:11:35

@U2J7JRTDX Meander’s pattern matcher is suited for this task (among others):

(m/find {:a {:b 1
             :c "irrelevant"}
         :d [1 2 3]
         :e "whatever"}
  {:a {:b ?b}
   :d ?d
   :as ?q}
  [?q ?b ?d])
;; =>
[{:a {:b 1, :c "irrelevant"}
  :d [1 2 3]
  :e "whatever"}
 1
 [1 2 3]]

xiongtx02:11:26

Ah, I knew this had to be a solved problem! Thanks!

noprompt02:11:48

You can even query maps for multiple answers.

noprompt02:11:50

(m/search {:a {:b 1
               :c "irrelevant"}
           :d [1 2 3]
           :e {:f "whatever"}}
  {?k {:as ?v}}
  [?k ?v])
;; =>
([:e {:f "whatever"}] [:a {:b 1, :c "irrelevant"}])

noprompt02:11:18

We’re located in the #meander channel for more on the topic of pattern matching/rewriting.

noprompt02:11:42

Its a work in progress but we can do some neat stuff if you have these kinds of problems 🙂

xiongtx02:11:56

Actually, I see that the ?q returned by meander is still contains keys that aren’t specified in the matching map, i.e. :c & :e.

xiongtx02:11:15

Here’s more what I had in mind, based on https://stackoverflow.com/a/40560433 (obviously not a comprehensive solution).

(defn pathwalk [f path e]
  (let [e' (f path e)]
    (cond
      (map? e')  (->> e'
                      (map (fn [[k x]] [k (pathwalk f (conj path k) x)]))
                      (into (empty e')))
      (coll? e') (->> e'
                      (map-indexed (fn [i x] (pathwalk f (conj path i) x)))
                      (into (empty e')))
      :else e')))

(defn match-only
  [match m]
  (let [bindings (atom {})
        form (atom {})]
    (pathwalk (fn [path v]
                (when (symbol? v)
                  (let [x (get-in m path)]
                    (swap! form assoc-in path x)
                    (swap! bindings assoc v x)))
                v)
              []
              match)
    [@form @bindings]))
(match-only '{:a {:b b}
              :d d}
            {:a {:b 1
                 :c "irrelevant"}
             :d [1 2 3]
             :e "whatever"})
;; => [{:a {:b 1}, :d [1 2 3]} {b 1, d [1 2 3]}]

noprompt20:11:43

@U2J7JRTDX Sorry for the late reply. It is possible to return only the part of the map that matched but it requires some advanced use of the library. If you know what you’re querying for (which is usually half the battle) you could simply construct the data on the right side as so:

(m/find {:a {:b 1
             :c "irrelevant"}
         :d [1 2 3]
         :e "whatever"}
  {:a {:b ?b}
   :d ?d}
  [{:a {:b ?b} :d ?d}
   {:b ?b}
   ?d])
;; =>
[{:a {:b 1}, :d [1 2 3]} {:b 1} [1 2 3]]

xiongtx01:11:42

Been using meander for value extraction in some of our tests & it’s been great! E.g. 🤢

(-> query
    graphql-request
    (get-in [:data :applications :edges])
    first
    (get-in [:node :app_accounts :edges])
    first
    :node
    (select-keys [:account_name]))
to 😄
(meander/match (graphql-request query)
  {:data
   {:applications
    {:edges
     [{:node
       {:app_accounts
        {:edges
         [{:node
           {:account_name ?account-name}}]}}}]}}}
  {:account_name ?account-name})
Great job!

noprompt01:11:53

Sweet! 👍

hiredman00:11:50

you can't really

hiredman00:11:51

I take that back, you can

hiredman01:11:44

I am back to can't. basically the way to build structures via unification requires and exact match, and featurec does not require an exact match, and core.logic doesn't provide relational operations on maps

xiongtx01:11:28

Hmm, so you’re saying unification can’t do a partial match?

hiredman01:11:48

right

🙁 4