This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-21
Channels
- # announcements (8)
- # babashka (12)
- # beginners (18)
- # biff (25)
- # calva (8)
- # clj-kondo (19)
- # clojure (53)
- # clojure-europe (3)
- # clojure-norway (3)
- # clojurescript (31)
- # emacs (9)
- # fulcro (12)
- # lsp (25)
- # membrane (1)
- # off-topic (58)
- # pathom (11)
- # pedestal (1)
- # proletarian (3)
- # re-frame (6)
- # releases (2)
- # shadow-cljs (7)
I'm doing lots of work in Python lately. Has anyone been able to leverage Pathom outside of Clojure projects to gain some advantage?
I have some thought about the semantics of nested entities I’d like to share. Pathom3 treats the attributes pointing to nested entities as a value whereas a relationship might make more sense. If the behaviour was a little different then it could allow for some useful queries (Assuming it would work).
I’m aware that Pathom3 is not designed to support a query as described in the snippet. The recommended approach being to “Pass data down” to allow a child to access an attribute from its parent.
The reason Pathom3 doesn’t can’t do this query is because family-member-surnames
has :com.example.family/members
as both it’s input and output.
However semantically, the family-member-surnames
isn’t actually transforming the :com.example.family/members
attribute at all. The attribute represents a relationship between the family
and person
entities and the resolver isn’t changing anything about that relationship at all. The same entities are returned, in the same order, and with no values changed, only added.
So the value of the com.example.family/members
attribute isn’t really children and their attributes, but instead a reference to the children.
So it would be reasonable to expect Pathom to treat attributes with nested data as relationships instead.
It could work by generating and caching a reference to all nodes/entities in nested data while running. Planning would detect nested input/output and dynamically create reverse lookup resolvers that will output a reference to a parent entity in exchange for child reference. And the parent reference could then be exchanged for parent attributes.
So each map within a deeply nested data-structure is assigned a reference by Pathom3, and then cached. And that reference can be used reach parent data.
Additionally, planning could detect and merge nested data across resolvers if the attribute is the same, which indicates it’s the exact same relationship. Same for a resolver that has the same nested attribute in the input and output.
Here’s an example of some code that doesn’t work but theoretically could.
(ns com.example.core16
(:require
[clojure.test :refer :all]
[com.wsscode.pathom3.connect.built-in.resolvers :as pbir]
[com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[com.wsscode.pathom3.interface.eql :as p.eql]))
(def family-db
(pbir/constantly-resolver
:com.example.family/db
{"Family1" {:com.example.family/surname "Smith"
:com.example.family/members [{:com.example.person/first-name "John"}
{:com.example.person/first-name "Mary"}]}}))
(def family-by-id
(pbir/attribute-table-resolver
:com.example.family/db
:com.example.family/id
[:com.example.family/surname
{:com.example.family/members [:com.example.person/first-name]}]))
(pco/defresolver family-member-surnames
[_env {:com.example.family/keys [members surname]}]
{
::pco/input [:com.example.family/surname {:com.example.family/members [:com.example.person/first-name]}]
::pco/output [{:com.example.family/members [:com.example.person/surname]}]}
{:com.example.family/members
(mapv (constantly {:com.example.person/surname surname}) members)})
(def env
(pci/register
[family-db
family-by-id
family-member-surnames]))
(deftest derive-person-surname-from-family-test
;; Fails
(is (= {:com.example.family/members
[{:com.example.person/first-name "John"
:com.example.person/surname "Smith"}
{:com.example.person/first-name "Mary"
:com.example.person/surname "Smith"}]}
(p.eql/process
env
{:com.example.family/id "Family1"}
[{:com.example.family/members
[:com.example.person/first-name
:com.example.person/surname]}]))))
I’ve also played around with Datascript and Pathom3 to apply this idea. It seems like it could work. However if possible, it would be preferable for the caching and references to be implicitly handled within Pathom3 as apposed to the explicit approach in the example. Here’s an example:
(ns com.example.core18
(:require
[clojure.test :refer :all]
[com.wsscode.misc.coll :as coll]
[com.wsscode.pathom3.connect.indexes :as pci]
[com.wsscode.pathom3.connect.operation :as pco]
[com.wsscode.pathom3.format.eql :as pf.eql]
[com.wsscode.pathom3.interface.eql :as p.eql]
[datascript.core :as d]))
(pco/defresolver family-members
[_ {family :db/id :keys [conn]}]
{::pco/output [{:com.example.family/members [:db/id :conn]}]}
{:com.example.family/members
(->> (d/pull @conn [:com.example.family/members] family)
:com.example.family/members
(mapv (fn [{:db/keys [id]}]
{:db/id id
:conn conn})))})
(pco/defresolver person-first-name
[_ {person :db/id :keys [conn]}]
{::pco/input [:db/id :conn]
::pco/output [:com.example.person/first-name]}
(d/pull @conn [:com.example.person/first-name] person))
(pco/defresolver person-full-name
[_ input]
{::pco/input [:com.example.person/first-name
{:com.example.person/family [:com.example.family/surname]}]
::pco/output [:com.example.person/full-name]}
(let [{:com.example.person/keys [first-name]
{:com.example.family/keys [surname]} :com.example.person/family} input]
{:com.example.person/full-name
(str first-name " " surname)}))
(pco/defresolver person-family
[_ {person :db/id :keys [conn]}]
{::pco/input [:db/id :conn]
::pco/output [{:com.example.person/family [:db/id :conn]}]}
{:com.example.person/family (-> (d/pull @conn [:com.example.family/_members] person)
:com.example.family/_members
first
(assoc :conn conn))})
(pco/defresolver family-surname
[_ {person :db/id :keys [conn]}]
{::pco/input [:db/id :conn]
::pco/output [:com.example.family/surname]}
(d/pull @conn [:com.example.family/surname] person))
(def env
(-> (pci/register [family-members person-first-name family-surname person-family person-full-name])
(update ::pf.eql/map-select-include coll/sconj :db)))
(def entity
(let [conn (d/create-conn {:com.example.family/members
{:db/cardinality :db.cardinality/many
:db/valueType :db.type/ref}})
{{:strs [smith-family]} :tempids}
(d/transact! conn [{:db/id "smith-family"
:com.example.family/surname "Smith"
:com.example.family/members [{:com.example.person/first-name "John"}
{:com.example.person/first-name "Mary"}]}])]
{:db/id smith-family
:conn conn}))
(p.eql/process
env
entity
[:com.example.family/surname
{:com.example.family/members [:com.example.person/first-name
:com.example.person/full-name
{:com.example.person/family
[:com.example.family/surname]}]}])
(I think starting the thread with a summarizing question/remark and then details in the thread would make it easier to digest this:pray: )
@U0FT7SRLP Thanks for the feedback. I made some small changes I hope make’s it easier to read.
hello @U3XCG2GBZ, I was looking at the first sources now, if I understand correctly, what you looking for is some way to merge the data that comes from sources under the same attribute. the problem with this is that it breaks an important invariant of how pathom processing works, there is: "once an attribute is set, its done, it can't change". I think that's what you mean when you say to threat as a relationship instead of a value. the problem is that, for pathom there is no such distinction, there is no concept of some sort of the value that could change after being set
the reason this invariant is important, is that it gives some sanity to the process, because if a value starts at something, is passed to some resolvers, and them at some point a new value emerges that changes it, it would affect the result of depending in the order of which the resolvers run, which would be caotic
it could be theoritically possible to try to observe all of that and make sure depences run in order to ensure the "full value" before sending it over, but its a really tricky problem as far as I see it
this is why you need to send the data down, because the children is processed after in the stack, so the parent isn't done, which means it can send things down, without mutating final values (by localizing the change down the stack)
this is also why we can't look up, fundamentally is because that parent value isn't ready, whcih means it could change after being looked up
makes sense?