How to create PersistentArrayMap preventing evaluation? - replace

My question is quite simple but weird at the same time, I would like to create a PersistentArrayMap avoiding evaluation at the same time that I would like to get the value inside this. What is the best solution? I mean
Imagine that I have this def:
(def queen "catherine the great")
And would like to do something like this (single quote):
'{:queen queen}
for sure the output is
=> {:queen queen}
But i expected do something like this
=> {:queen "catherine the great"}
I know that I can just do it
(array-map :queen queen)
But in my case I would like only evaluate some information cause my map is more complicated, like a datomic query:
'{:find [(pull $ ?c [*])]
:with []
:in [$ ?queen-name]
:where [
[$ ?c :queen/name ?queen-name]
]
:args [queen-name]}
for this case I would like only to evaluate queen-name.
My question is, there's a simple way to do it? Maybe using update?
something like this?
(assoc-in '{:find [(pull $ ?c [*])]
:with []
:in [$ ?queen-name]
:where [
[$ ?c :queen/name ?queen-name]
]
:args []} [:args] ["catherine the great"])

For both examples, you can use syntax-quote and unquote:
user=> (def queen-name "catherine the great")
#'user/queen-name
user=> `{:queen ~queen-name}
{:queen "catherine the great"}
user> {:find '[(pull $ ?c [*])]
:with '[]
:in '[$ ?queen-name]
:where '[
[$ ?c :queen/name ?queen-name]
]
:args `[~queen-name]}
{:find [(pull $ ?c [*])],
:with [],
:in [$ ?queen-name],
:where [[$ ?c :queen/name ?queen-name]],
:args ["catherine the great"]}

If you want to do this in general, you can use tupelo.quote.
(ns demo.core
(:require [tupelo.quote :as q]))
; problem: free symbols a and b are fully-qualified using current ns
`[a b ~(+ 2 3)] => [demo.core/a
demo.core/b
5]
(q/tmpl-fn '[a b (insert (+ 2 3))]) => [a b 5]
(let [a 1 b 2]
(q/tmpl [a b (insert (+ 2 3))])) => [1 2 5]
(is= [1 [2 3 4] 5] (q/tmpl [1 (insert (t/thru 2 4)) 5]))
(is= [1 2 3 4 5] (q/tmpl [1 (splice (t/thru 2 4)) 5]))
For Datomic in particular, you can use query inputs:
(def some-name "John Lennon") ; parameter
;; query
(d/q '[:find ?release-name ; query pattern (quoted)
:in $ ?artist-name
:where [?artist :artist/name ?artist-name]
[?release :release/artists ?artist]
[?release :release/name ?release-name]]
db, some-name ; inputs (not quoted)
)
with result
#{["Power to the People"]
["Unfinished Music No. 2: Life With the Lions"]
["Live Peace in Toronto 1969"]
["Live Jam"]
...}

Related

ClojureScript map destructure with defaults filled in?

Does Clojure/Script offer a way to build a destructured map out of the arguments plus filled-in defaults in case the keys weren't supplied in the call?
Consider this example (that doesn't quite do what the code implies by a quick glance). Does clojure provide a way to build the map prompt with these four keys and values either from the caller or the defaults. I hate to think I have to repeat these key names two more times to get what I am after.
(re-frame/reg-event-db
:open-prompt
(fn [db [_ {title :title
text :text
label-yes :label-yes
label-no :label-no
:or {title "Confirm"
text "Are you sure?"
label-yes "Ok"
label-no "Cancel"}
:as prompt}]]
(-> db
(update :state conj :modal-prompt)
(assoc :prompt prompt))))
After reviewing the official documentation page about destructuring, I don't think that Clojure proposes a more convient way of doing that.
But just by curiosity, I was wondering what is the code generated by destructuring, because I'm expecting it relies on macro stuff. Let consider this toy example:
(def my-map {:text "Some text"})
(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt))
;; => "I got Confirm from {:text \"Some text\"}"
(macroexpand '(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt)))
;; => (let*
;; [map__12555
;; my-map
;; map__12555
;; (if
;; (clojure.core/seq? map__12555)
;; (clojure.lang.PersistentHashMap/create
;; (clojure.core/seq map__12555))
;; map__12555)
;; prompt
;; map__12555
;; title
;; (clojure.core/get map__12555 :title "Confirm")]
;; (str "I got " title " from " prompt))
So as you can see, after a macro expansion, the :or mechanism which allows to specifies default value relies on clojure.core/get.
In this particular example, title is affected by (clojure.core/get map__12555 :title "Confirm") form. It's a way to avoid repeating the title variable, but does it worth it?
You can also check the source code of the destructuring macro to get full details about it, but personally I found it pretty difficult to handle ^^'.
it is doable, maybe not very practical though, but nice for self education:
let's begin with making up the function what would be special binding case.
let's say, we want to pass vectors of length 2 or 3, where vector of 2 will represent the simple binding map key-value pair like [:as abc] or [a :a], and the vector of size 3 would be k-v-default triple: [a :a "my default"]. The example of it's usage:
(bindings-preproc [['a 1 "asd"]
['b 2 "ddd"]
[:or {'x 10}]
[:as 'whole]])
resulting to
{a 1, b 2, :or {x 10, a "asd", b "ddd"}, :as whole}
this function could look like this:
(defn bindings-preproc [decls]
(let [defaults (into {} (keep (fn [decl]
(when (and (not (keyword? (first decl)))
(= 3 (count decl)))
(let [[nm _ default] decl]
[nm default])))
decls))
all-kvs (apply assoc {} (mapcat (partial take 2) decls))]
(update all-kvs :or merge defaults)))
(this one doesn't include error checks for the sake of illustrative simplicity)
The next thing is to employ it inside the binding macros. The idea to make bindings-preproc a macro fails, because binding forms are checked for validity before the inner macros are evaluated.
But still we have a feature, that would help, namely reader tags. They are used for example when you use #inst syntax. Since these reader tags are processed at read-time, before any macros are getting expanded, we can plug our preprocessor in.
(here i will use actual reference update, to demonstrate it from repl, but in real projects you would declare these tags in a special file)
user> (alter-var-root
#'default-data-readers
assoc 'my/reader #'user/bindings-preproc)
;;=> {uuid #'clojure.uuid/default-uuid-reader,
;; inst #'clojure.instant/read-instant-date,
;; my/reader #'user/bindings-preproc}
so, now we can try to make it work:
(defn f [#my/reader [[a :a 10]
[b :b 20]
[z :z]
[:keys [k1 k2 k3]]
[[c1 c2 & cs] :c]
[:or {z 101
k3 :wooo}]
[:as whole]]]
{:a a :b b :c1 c1 :c2 c2 :cs cs :z z :k1 k1 :k2 k2 :k3 k3 :whole whole})
user> (f {:a 1000 :c [:one]})
;;=> {:cs nil,
;; :c2 nil,
;; :z 101,
;; :c1 :one,
;; :k3 :wooo,
;; :b 20,
;; :whole {:a 1000, :c [:one]},
;; :k1 nil,
;; :k2 nil,
;; :a 1000}
user> (let [a 10
b 20
#my/reader [[x :x 1]
[y :y 2]
[z :z 100]] {:z 432}]
[a b x y z])
;;=> [10 20 1 2 432]
I like to make a map of all default values, then use into or similar to fuse the user-supplied values into the map of default values. For example:
(ns tst.demo.core
(:use tupelo.core tupelo.test) )
(def stuff-default {:a 1 :b 2})
(defn apply-defaults
[arg]
(let [stuff (glue stuff-default arg)] ; or use `into`. Last one wins, so put defaults first
(with-map-vals stuff [a b]
(newline)
(spyx a)
(spyx b))
stuff))
(dotest
(is= (apply-defaults {}) ; no inputs => all default values
{:a 1, :b 2})
(is= (apply-defaults {:a 100}) ; some inputs => partial defaults
{:a 100, :b 2})
(is= (apply-defaults {:a 100, :b 200}) ; all inputs => no defaults used
{:a 100, :b 200}))
Here glue is like into but with more error checking. We also use tupelo.core/with-map-vals to destruct the map, with less repetition than native Clojure destructuring (vals->map does the reverse).
The output is:
-------------------------------
Clojure 1.10.1 Java 14
-------------------------------
a => 1
b => 2
a => 100
b => 2
a => 100
b => 200
Ran 2 tests containing 3 assertions.
0 failures, 0 errors.

Clojure: destructure and rename with {:keys [...]}

Is it possible to both destructure and rename keys in one go?
Consider this:
(let [{:keys [response]} {:response 1}]
(println response))
However, if I want to instead refer to 1 as my-response, I have to do something like:
(let [{:keys [my-response]} (clojure.set/rename-keys {:response 1} {:response :my-response})]
(println my-response))
Obviously this does not work with defn destructuring.
Is there any way in Clojure to both destructure and rename keys?
Use destructuring without :keys:
(let [{my-response :response} {:response 1}]
(println my-response))
{:keys [response]} is syntactic sugar for {response :response}.
Here you go:
(let [{:keys [response]} {:response 1}
my-response response]
(println my-response))
For a better answer refer to https://stackoverflow.com/a/57592661/2757027.
This answer is much closer to the question, but technically not single step. but this doesn't involve any complicated de-structuring.
If you don't mind using a library, a more powerful destructuring tool is available from tupelo.core/destruct. Here is an example:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(let [info {:a 777
:b [2 3 4]}
mania [{:a 11} {:b 22} {:c [7 8 9]}]]
(let [z ::dummy]
(destruct [info {:a z
:b [d e f]}
mania [{:a ?} BBB {:c clutter}]]
(is= z 777)
(is= [d e f] [2 3 4])
(is= a 11)
(is= BBB {:b 22})
(is= clutter [7 8 9])))))
So you can see that inside the destruct expression, the symbols z, d, e, f, BBB, and clutter are given the corresponding values from the input variables info and mania. The special symbol ? is interpreted to mean that the keyword :a creates a symbol a to receive the value 11.

DataScript / datahike rules returning nothing

I try to search for a string in more than one field of a database in datahike. So far without success. Here is my best effort approach so far:
(ns my.ns
(:require [clojure.string :as st]
[datahike.api :as dh]))
(def card-db [[1 :card/name "CardA"]
[1 :card/text "Text of CardA mentioning CardB"]
[2 :card/name "CardB"]
[2 :card/text "A mystery"]])
(def rules '[
[
(matches-name ?ent ?fn ?str)
[?ent :card/name ?name]
(?fn ?name ?str)]
[(matches-text ?ent ?fn ?str)
[?ent :card/text ?text]
(?fn ?text ?str)
]
])
(defn my-search [db search-strs]
(dh/q '[:find ?e
:in $ % ?fn [?str ...]
:where
[?e :card/name ?name]
#_[(?fn ?name ?str)] ;; this finds CardB
(or
(matches-name ?e ?fn ?str)
(matches-text ?e ?fn ?str)
) ;; this finds nothing
]
db rules st/includes? search-strs))
#_(count (my-search card-db ["CardB"]))
Expected result: 2
Actual result: 0
The solution needn't use rules as far as I'm concerned. It should just return a match if a string is found in at least one of multiple fields.
I'm using [io.replikativ/datahike "0.1.1"]
DataScript doesn’t support or yet. I think Datahike does neither. But you can emulate it using rules. Try:
(def rules '[[(matches ?ent ?fn ?str)
[?ent :card/name ?name]
(?fn ?name ?str)]
[(matches ?ent ?fn ?str)
[?ent :card/text ?text]
(?fn ?text ?str)]])
(defn my-search [db search-strs]
(dh/q '[:find ?e
:in $ % ?fn [?str ...]
:where [?e :card/name ?name]
(matches ?e ?fn ?str)]
db rules st/includes? search-strs))

Possible to get enum value via Datomic pull syntax?

In the mbrainz sample data, the :artist/type is an enum. Is it possible to pull the value of the enum out of :db/ident and associate it as the value of the :artist/type key using pull syntax?
This is as close as I could get:
[:find (pull ?e [:artist/name {:artist/type [:db/ident]}])
:where
[?e :artist/name "Ray Charles"]
]
;;=> [[{:artist/name "Ray Charles", :artist/type {:db/ident :artist.type/person}}]]
Is it possible to use pull syntax to reshape the result into something like this?
;;=> [[{:artist/name "Ray Charles", :artist/type :artist.type/person}]]
I don't think you can do it using the Pull API the way you are seeking. You may find that it is easier to use the Tupelo Datomic library:
(require '[tupelo.datomic :as td]
'[tupelo.core :refer [spyx]] )
(let [x1 (td/query-scalar :let [$ db-val]
:find [ ?e ]
:where [ [?e :artist/name "Ray Charles"] ] )
x2 (td/entity-map db-val x1)
]
(spyx x1)
(spyx x2)
)
which gives the result:
x1 => 17592186049074
x2 => {:artist/sortName "Charles, Ray", :artist/name "Ray Charles",
:artist/type :artist.type/person, :artist/country :country/US,
:artist/gid #uuid "2ce02909-598b-44ef-a456-151ba0a3bd70",
:artist/startDay 23, :artist/endDay 10, :artist/startYear 1930,
:artist/endMonth 6, :artist/endYear 2004, :artist/startMonth 9,
:artist/gender :artist.gender/male}
So :artist/type is already converted into the :db/ident value and you can just pull it out of the map.
You can use specter on the result that the pull expression returns:
(->> pull-result
(sp/transform (sp/walker :db/ident) :db/ident))
The value of key :db/ident is extracted for every map that has that key.
Was quite easy to do with postwalk
for any pulled :db/ident you can transform with this function
(defn flatten-ident [coll]
(clojure.walk/postwalk
(fn [item] (get item :db/ident item)) coll))

How does the not clause work in Datomic?

I am trying to find latitudes which fall between two inputs. My query:
(defn- latlngs-within-new-bounds
[db a w]
(d/q '[:find ?lat
:in $ ?a ?w
:where
[ ?e :location/lat ?lat]
[(>= ?lat ?a)]
(not
[(>= ?lat ?w)])]
db a w))
My error:
3 Unhandled com.google.common.util.concurrent.UncheckedExecutionException
java.lang.RuntimeException: Unable to resolve symbol: ?lat in this
context
2 Caused by clojure.lang.Compiler$CompilerException
1 Caused by java.lang.RuntimeException
Unable to resolve symbol: ?lat in this context
Util.java: 221 clojure.lang.Util/runtimeException
Any help with understanding what's wrong with my query would be appreciated. Bonus points if you can also use Datomic rules to factor out the in-bounds part of each half.
Your code seems to work for me with datomic-free 0.9.5173:
(defn- latlngs-within-new-bounds
[db a w]
(d/q '[:find ?lat
:in $ ?a ?w
:where
[ ?e :location/lat ?lat]
[(>= ?lat ?a)]
(not
[(>= ?lat ?w)])]
db a w))
(latlngs-within-new-bounds
[[1 :location/lat 1]
[2 :location/lat 2]
[3 :location/lat 3]
[4 :location/lat 4]
[4 :location/lat 5]]
2 4)
=> #{[2] [3]}