This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-04
Channels
- # announcements (1)
- # architecture (16)
- # asami (30)
- # babashka (1)
- # beginners (17)
- # calva (4)
- # clj-kondo (4)
- # clojure (32)
- # clojure-austin (2)
- # clojure-dev (16)
- # clojure-europe (33)
- # clojure-mexico (1)
- # clojurescript (40)
- # data-science (9)
- # emacs (33)
- # fulcro (1)
- # jackdaw (4)
- # malli (25)
- # off-topic (34)
- # pathom (23)
- # react (16)
- # reagent (8)
- # releases (5)
- # vim (46)
I may have asked this before, but how would one go about querying RDF lists? In SPARQL you can use property paths to achieve this relatively easily:
SELECT *
WHERE {
:myNodeShape (sh:and/rdf:rest*/rdf:first)* ?otherShapes .
}
AFAIK Asami supports *
and +
, but not property paths (`/`). Is that right?
Any tips on how to deal with this?That will get you down nested lists, which asami can do, but I’m guessing that’s not what you want?
:find
:where [:myNodeShape sh:and ?head]
[?head :rdf/rest* ?list]
[?list :rdf/first ?otherShapes]
This binds ?head
to the first list node.
Using :rdf/rest*
means that ?list
will bind to every node in the list, including the ?head
node (which is zero steps away from the node bound to ?head
).
For each one of these nodes, you get the :rdf/first
value, which is the data in the list.
Your original query is a property path that is very strange…
Step along sh:and
. From there, follow zero or more rdf:rest
steps (which will be everything in the list), and then follow the rdf:first
step to the entry in the list.
From each of these list elements, recurse. i.e. follow a sh:and
to the head of a list, and traverse to it’s nodes, recursing on each of these as well.
I had never considered nested transitive closures. This will be interesting to encode when I get to that part of SPARQL 😬
Hmm reading back I might have butchered the example a bit. I'll look at it closer tomorrow, it's bed time here now. But indeed, nested transitivity is probably a cool and powerful thing to support anyways :)
@U051N6TTC To provide a little more context: I want to recursively fetch all node shapes being referred to through sh:and
.
So if I have:
:AShape
a sh:NodeShape ;
sh:and ( :BShape :CShape )
.
:BShape
a sh:NodeShape ;
sh:and ( :DShape )
.
:CShape a sh:NodeShape .
:DShape a sh:NodeShape .
:EShape a sh:NodeShape .
I'd like to obtain:
:AShape
:BShape
:CShape
:DShape
> From there, follow zero or more rdf:rest
steps (which will be everything in the list), and then follow the rdf:first
step to the entry in the list.
Also, following rdf:rest*
does not give everything in the list, but everything but the first element, right?
OK, if you have nested shapes, then are you going for an arbitrary level of nesting, or just 1 level?
Arbitrary level of nesting was the idea
I see now I was indeed mistaken in how I read the rdf:rest*
.
What is the magic set approach? Something "Google"-able suffices
INSERT {?outer internal:includes ?inner}
WHERE {
?outer a sh:NodeShape ;
sh:and/rdf:rest*/rdf:first ?inner
}
Then you can do simple queries of:
SELECT ?other
{ :myNodeShape internal:includes ?other }
It just means that you instantiate extra edges that meet the requirements of what you’re looking for
You can do it without instantiation if you use subqueries… Which you can actually do with Asami, but I never showed anyone how. It’s not (yet) part of the query syntax. Instead, you feed the results of one query into the input of the next
Ah right, so those are SPARQL queries ^. And I use these to CONSTRUCT
(instead of INSERT
, right?) triples like you suggest, and then fetching them is straightforward, also in Asami. I get the gist at least
Ah, wait… I forgot that the INSERT/WHERE id still only doing a single step. Sigh. You can loop on that in code, but it’s messy
I’m in a meeting, so I only have half my attention on the problem. Consequently, the following is probably wrong. But I would use this kind of approach:
(loop [nodes #{:myNodeShape}]
(let [part (q '[:find [?inner ...]
:in $ [?node ...]
:where [?node :a :sh/NodeShape]
[?node :sh/and ?list]
[?list :rdf/rest* ?node]
[?node :rdf/first ?inner]]
my-db nodes)
next-nodes (into nodes part)]
(if (= next-nodes nodes)
nodes
(recur next-nodes))))
The idea is, use the output of a query as the input of the next query. That’s how subqueries usually work too (you can thread queries into each other with ->>
)
And that’s how I do transitivity. I collect things in sets, and loop on the query. When the set doesn’t change, you know you’ve finished the search
It’s a little more low-level, which allows me to filter out things that have already been seen, reducing the size of each step, but that’s just an optimization
I’ve corrected it now, but the :in
clause needs “relations bindings” because that’s the result format from a prior query
I'm going to have to read this a few times and mull over it somewhat to understand it in detail. But I understand the gist, and I see a solution in this indeed. Thanks, I'm learning a lot as always!
I just realized… I needed to extract results, (which I could do, by mapping first
across the output), but I also needed to wrap nodes
in a seq, since it’s not actually a prior result.
It’s just easier to project the results into a seq instead. Fixed.
Haha don't be sorry. Grateful for your help, even during costly meetings 😉