core-logic

xiongtx 2019-11-16T00:51:11.022900Z

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?

noprompt 2019-11-16T20:11:43.030300Z

@xiongtx 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]]

xiongtx 2019-11-19T01:27:42.030500Z

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!

noprompt 2019-11-19T01:34:53.030800Z

Sweet! 👍

2019-11-16T00:57:50.023500Z

you can't really

2019-11-16T00:58:51.023800Z

I take that back, you can

2019-11-16T01:00:44.026100Z

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

xiongtx 2019-11-16T01:07:28.026600Z

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

2019-11-16T01:08:48.026900Z

right

🙁 1
noprompt 2019-11-16T01:59:35.027900Z

@xiongtx 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]]

noprompt 2019-11-16T01:59:53.028100Z

http://github.com/noprompt/meander

xiongtx 2019-11-16T02:03:26.028800Z

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

noprompt 2019-11-16T02:03:48.029Z

You can even query maps for multiple answers.

noprompt 2019-11-16T02:03:50.029200Z

(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"}])

noprompt 2019-11-16T02:04:18.029400Z

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

noprompt 2019-11-16T02:04:42.029600Z

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

xiongtx 2019-11-16T02:51:56.029800Z

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.

xiongtx 2019-11-16T02:57:15.030Z

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