2023-04-22

well holy shit

may 15 to aug 15

super metroidvania month (3 month jam)

sometimes you git pull and then

DONE saturday clawe hacking

> DONE restore sub-todos on todos

> DONE ingest clips/gifs as events along with screenshots

> improve screenshot/gif viewer

> DONE localstorage for widgets open/closed state

DONE saturday mind gardening

> DONE clawe workspace and clawe note merging

> DONE all workspace notes merged into normal notes

> DONE clawe overview stream/video outline

> dino garden note rewrite

datascript resources

getting going with some datascript learning

Starting the datascript playground:

https://github.com/markbastian/datascript-playground

First file in 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.

"peta(l) to the meta(l)" - duaa quotes