2025-10-06, 1126h, Building Fregean predicates from org-roam nodes

So I didn't do a good job writing much about it in my archive as I was reading about it, but I recently did a lot of reading into the Semantic Web, which led me to reading about resource description format and finally onto things like Fregean first-order predicates. This all coalesced around something I'd been slowly doing in my nodes anyway, which was writing a property into each Org-roam node, which has changed name and shape a few times, but I'm currently calling it the ~node-description~ property.

One way I'm thinking of this property is as a node's secondary intension toward relational dynamics, and given that, the structure that seemed best was Fregian ~subject-predicate-object~ triplets, where each part of the triplet is another node in the database.

Conveniently, this lines up with where a lot of contemporary Web and large language model technologies are moving.

Here's a demonstration of what a node's property drawer looks like, in GnoponEmacs:

:PROPERTIES:
:ID: 39a3beb3-898b-4494-b1d2-144c7a38c3dd
:node-description: ((resource type . babble) (author . emsenn) (publication . emsenn.net) (creation date . 2025-10-06)
:END:

That probably isn't super readable like that. To the computer, it can parse as a list of two-part items: a predicate and an object, and it knows the subject for each is the ID, forming that ~subject-predicate-object~ triplet.

But in plainer rendering, here's what that says the node description is:

resource type :: babble

author :: emsenn

publication :: emsenn.net

creation date :: 2025-10-06

As you can see, every part of every triplet is itself has two parts: an ~ID~ and ~sign~ (~d542...ecb~ and ~babble~).

(defun gpe/build-predicates ()
(cl-loop
for (id file level pos todo priority scheduled deadline title props)
in (org-roam-db-query
[:select [id file level pos todo priority scheduled deadline title properties]
:from nodes
:where (like properties '"%resource-description%")])
collect
(let* ((raw-rd (alist-get "RESOURCE-DESCRIPTION" props nil nil #'string=))
(alist (when raw-rd (ignore-errors (read raw-rd)))))
(when alist
(cl-loop
for (pred . obj) in alist
collect
(let* ((parse-link
(lambda (s)
(when (string-match "\\[\\[id:\\([^]]+\\)\\]\\[\\([^]]+\\)\\]\\]" s)
(list :id (match-string 1 s)
:sign (match-string 2 s)))))
(pred-str (replace-regexp-in-string "] \\[" "][" (format "%s" pred)))
(obj-str (replace-regexp-in-string "] \\[" "][" (format "%s" obj)))
(pred-link (funcall parse-link pred-str))
(obj-link (funcall parse-link obj-str)))
(list
:subject-id id
:subject-sign title
:predicate-id (plist-get pred-link :id)
:predicate-sign (plist-get pred-link :sign)
:object-id (plist-get obj-link :id)
:object-sign (plist-get obj-link :sign))))))))
(gpe/build-predicates)

(defun gpe/collect-predicates-from-node (node)
(let* ((raw-rd (alist-get "RESOURCE-DESCRIPTION"
(org-roam-node-properties node) nil nil #'string=))
(alist (when raw-rd (ignore-errors (read raw-rd)))))
(when alist
(cl-loop
for (pred . obj) in alist
collect
(let* ((parse-link
(lambda (s)
(when (string-match "\\[\\[id:\\([^]]+\\)\\]\\[\\([^]]+\\)\\]\\]" s)
(list :id (match-string 1 s)
:sign (match-string 2 s)))))
(pred-str
(replace-regexp-in-string "] \\[" "]["
(format "%s" pred)))
(obj-str
(replace-regexp-in-string "] \\[" "]["
(format "%s" obj)))
(pred-link (funcall parse-link pred-str))
(obj-link (funcall parse-link obj-str)))
(list
:subject-id id
:subject-sign title
:predicate-id (plist-get pred-link :id)
:predicate-sign (plist-get pred-link :sign)
:object-id (plist-get obj-link :id)
:object-sign (plist-get obj-link :sign)))))))
(defun gpe/filter-predicates-by (predicates predicate-id object-id)
(cl-loop for entry in predicates
when (and (string= (plist-get (car entry) :predicate-id)
predicate-id)
(string= (plist-get (car entry) :object-id)
object-id))
collect (plist-get (car entry) :subject-id)))
(let* ((ids
(gpe/filter-predicates-by
(gpe/build-predicates)
"edaef069-c68d-46aa-a970-d3cb7fe3165c"
"d542e8d2-3512-47a0-adbb-4a393e161ecb"))
(nodes (mapcar #'org-roam-node-from-id ids))
(output
(mapconcat
(lambda (node)
(format "- %s"
(org-roam-node-id node)
(org-roam-node-title node)))
nodes
"\n")))
output)

(gpe/ritm/render-list-of-matches '(("edaef069-c68d-46aa-a970-d3cb7fe3165c" . "d542e8d2-3512-47a0-adbb-4a393e161ecb")))