HydraCG / Specifications

Specifications created by the Hydra W3C Community Group
Other
138 stars 26 forks source link

Should blank nodes be ignored in member assertion by default? #248

Open tpluscode opened 1 year ago

tpluscode commented 1 year ago

Describe the requirement

I have been working with member assertions lately and I figured that in the current design they do not make sense when their subject/property/object are blank nodes

Hydra-agnostic example

In my particular example, I wanted to express a collection of languages in which known books are written. In SPARQL I can express this as

SELECT ?language {
  ?book dcterms:language ?language .
  ?book a bibo:Book .
}

To turn that into a member assertion, I first got hydra:property dcterm:language but the hydra:subject needed to be something that expresses more patterns.

I used SHACL to create a variable for the ?book in query. My custom implementation converts that into query patterns above

<book-languages> 
  hydra:memberAssertion
    [
      hydra:property dcterms:language ;
      hydra:subject
        [
          a sh:NodeShape ;
          sh:targetClass bibo:Book ;
        ] ;
    ] ;
.

Proposed solutions

There is nothing in the spec that disallows that but I figured that a blank node has potentially undetermined meaning for a generic client or server. I propose that we explicitly state that if the hydra:subject/predicate/object is a blank node it SHOULD be ignored by a generic client, unless they explicitly know how to support that particular node (such as sh:NodeShape here).

That is to prevent queries like []dcterms:language ?language .

Further work

I also propose to describe the use of SHACL or OWL in their respective extensions.

alien-mcl commented 1 year ago

I think your approach is an overkill - I believe whole memberAssertion block should look like this:

<book-languages> 
  hydra:memberAssertion
    [hydra:property dcterms:language],
    [
      hydra:property rdf:type;
      hydra:object bibo:Book
    ]
.

It should be interpreted as each resource at book-languages has a predicate of dcterms:language and another predicate of rdf:type of which value is bibo:Book. As for having a blank node used as a value of hydra:object in my example - indeed it might be troublesome for the client to figure it out what's inside. We could add a statetement in the spec that recommends using non-blank resources here.

tpluscode commented 1 year ago

This is not how this works :)


First of all, we should require that there are always two properties. Otherwise, what do you suppose is the behavior when it only has hydra:property?

You say

SELECT ?member WHERE {
  ?member dcterms:language ?foo
}

but why not the reverse?

SELECT ?member WHERE {
  ?foo dcterms:language ?member
}

This is why in my implementation I require that a member assertion always has two of the hydra:(subject|predicate|object) properties.


The second member assertion also does not mean how you interpreted it. It reads as

Every member of <book-languages> has rdf:type equal bibo:Book

SELECT ?member WHERE {
  ?member rdf:type bibo:Book
}

The assertions are simple. Else, how would a client know to interpret bibo:Book at face value as opposed to "objects which are instances of bibo:Book? Not to mention that you reversed its position


We could add a statetement in the spec that recommends using non-blank resources here.

That is exactly what I proposed above: a blank node it SHOULD be ignored by a generic client. Unless they know how to handle them, such as using using SHACL (or OWL) to describe more complex patterns

tpluscode commented 1 year ago

Here's a potentially more real-life example of what could be achieved when using SHACL for some crazy-ass member assertions.

Imagine you use singleton property style of property attributes (maybe that would be possible in RDF* too but I won't be able to figure that our on the spot). The canonical example is marriage

# Bob married Jane on October 10th 2000
<Bob> ex:married#1 <Jane> .
ex:married#1 rdfs:subPropertyOf ex:married ; schema:date "2000-10-10" .

# Frank married Lucy on May 20th 1995
<Frank> ex:married#2 <Lucy> .
ex:married#2 rdfs:subPropertyOf ex:married ; schema:date "1995-05-20" .

<Bob> a schema:Person .
<Jane> a schema:Person .
<Frank> a schema:Person .
<Lucy> a schema:Person .

ex:married a owl:SymmetricProperty .

Here's a collection of people married before 2000

<married-before-y2k>
  a hydra:Collection ;
  hydra:memberAssertion [
    hydra:object [ 
      a sh:NodeShape ; 
      sh:targetClass schema:Person ;
    ] ;
    hydra:property [
      a sh:NodeShape ;
      sh:property [ 
        sh:path schema:date ;
        sh:maxExclusive "2000-01-01"^^xsd:date ;
      ] , [
        sh:path rdfs:subPropertyOf ;
        sh:hasValue ex:married ;
      ]
    ] ;
  ] ;
.

A hypothetical collection handler would be able to translate these member assertion shapes to a query like

SELECT ?member 
WHERE {
  ?member ?property ?spouse .

  # hydra:object assertion
  ?spouse rdf:type schema:Person .

  # hydra:property assertion(s)
  ?property rdfs:subPropertyOf ex:married ; schema:date ?date .
  FILTER ( ?date < "2000-01-01"^^xsd:date )
}