may 15 to aug 15
super metroidvania month (3 month jam)
Starting the datascript playground:
First file in the datascript-playground
Much better cloned and jacked-into than read via browser
Why can't we read github repos in a repl?
--
Some raw datomic examples: lucene dbs call this 'projection' postgres has some other name.
There's no reason for all the complexity - it's a fine default.
;;Pull
(d/pull test-db [:name :age :food] 1)
(d/pull-many test-db [:name :age :food] [1 2 4])
(d/pull test-db '[*] [1 2 4])
Here we have to opt-in (via
'[*]
) to pull all the fields for an entity.
Otherwise, it's just keywords for data. The keys are usually
namespaced, like
:user/name
,
:db/id
, etc, and
they tend to correlate with keywords used by your code
(domain logic, frontend components).
--
A glance at the syntax at the heart of real world datalog usage:
;; example with Datascript, from https://github.com/markbastian/datascript-playground
;; Get attributes of people
(let [a (d/db-with
(d/empty-db)
[{:name "Becky" :age 21 :color :red}
{:name "Mark" :age 23 :size 1}
{:name "Chloe" :age 9}
{:name "Chloe" :age 90}
{:name "Jenny" :age 18}])]
(d/q
'[:find ?attr
:where
[?e :name "Mark"]
[?e ?attr]]
a))
maps go in, then it gets indexed by whatever you want.
Then, use datalog to pull out whatever - use data, schema, names, your domain.
And you can write basic unit tests in just a few lines of code - the apis are very simple.
--
Datalog is constraint-based, and those constraints can be expressed via normal clojure functions.
Here we define a status function to return a more expressive classification than just an integer age.
;; example with Datascript, from https://github.com/markbastian/datascript-playground
;; Define custom fn.
(defn status [age]
(if (< age 21) :minor :adult))
;; Add custom fields using above function
(d/q '[:find ?name ?status
:where
[?e :name ?name]
[?e :age ?age]
;; Use custom function. Must be namespace-qualified.
[(datascript-playground.queries/status ?age) ?status]]
test-db)
Maybe a more interesting brain teaser:
;; Join favorite foods on name and add some custom data
(let [a (d/db-with
(d/empty-db)
[{:name "Becky" :age 21}
{:name "Mark" :age 23}
{:name "Chloe" :age 9}
{:name "Chloe" :age 90}
{:name "Jenny" :age 18}])
b (d/db-with
(d/empty-db)
[{:name "Mark" :food "Tacos"}
{:name "Chloe" :food "Tots"}])]
(d/q
'[:find ?n ?f ?a ?status
:in $a $b
:where
[$a ?e :name ?n]
[$a ?e :age ?a]
[$b ?x :name ?n]
[$b ?x :food ?f]
[(datascript-playground.queries/status ?a) ?status]]
a b))
;; =>
#{["Chloe" "Tots" 90 :adult]
["Chloe" "Tots" 9 :minor]
["Mark" "Tacos" 23 :adult]}
And for a there-is-no-spoon moment:
;; Same join, but with destructured args.
(let [a (d/db-with
(d/empty-db)
[{:name "Becky" :age 21}
{:name "Mark" :age 23}
{:name "Chloe" :age 9}
{:name "Chloe" :age 90}
{:name "Jenny" :age 18}])
b [["Mark" "Pizza"]
["Chloe" "Tots"]]]
(d/q
'[:find ?name ?food ?age
:in $a [[?name ?food]]
:where
[$a ?e :name ?name]
[$a ?e :age ?age]]
a b))
--
Other times, datalog feels like a weird kind of calculator:
What kind of weird mathing is going on here?
;; Predicate/function goodness
(d/q
'[:find ?celsius .
:in ?fahrenheit
:where
[(- ?fahrenheit 32) ?f-32]
[(/ ?f-32 1.8) ?celsius]]
32)
Could use a function, but why not use the database?
(d/q
;; query
'[:find [?prefix ...]
:in [?word ...]
:where [(subs ?word 0 5) ?prefix]]
;; inputs
["hello" "antidisestablishmentarianism"])
--
Just barely got this working... I suspect there's a better way (with only one query)
(defn list-todos-with-children
[{:keys [n filter-pred] :as opts}]
(cond->>
(db/query
'[:find (pull ?e [*])
:where
[?e :doctor/type :type/todo]])
true (map first)
filter-pred (filter filter-pred)
n (take n)
true
(map (fn [td]
(db/query
'[:find (pull ?e [*])
:in $ ?db-id
:where [?e :org/parents ?db-id]]
(:db/id td))))))
(comment
(list-todos-with-children
{:n 3
:filter-pred
(fn [item] (string/includes?
(:org/name-string item)
"saturday"))})
(db/datoms :eavt 20373)
(db/query
'[:find (pull ?e [*])
:in $ ?db-id
:where [?e :org/parents ?db-id]]
20362)
(db/query
'[:find (pull ?e [*])
:in $ ?id
:where [?e :org/parent-ids ?id]]
#uuid "690b842f-a579-44a2-97cc-6cc31bef41ee")
(->
(garden/daily-path)
(garden/path->nested-item)))
Eventually landed here:
(defn list-todos-with-children
[{:keys [n filter-pred] :as opts}]
(cond->>
(db/query
'[:find
(pull ?e [:org/name-string :org/short-path :db/id])
(pull ?c [:org/name-string])
:where
[?e :doctor/type :type/todo]
[?c :org/parents ?e]])
filter-pred (filter (comp filter-pred first))
true
(reduce (fn [acc [todo child]]
(update acc
(:db/id todo)
(fn [td]
(if-not td
(assoc todo :org/items [child])
(update td :org/items concat [child])))))
{})
true vals
n (take n)))
Though I think the above excludes todos _without_ children.