Consider the following RDF:
semapi:BaseClass a rdfs:Class;
rdfs:subClassOf rdfs:Class .
semapi:hasChainTo a rdf:Property;
rdfs:domain semapi:BaseClass;
rdfs:range semapi:BaseClass .
semapi:DerivedClass a rdfs:Class; rdfs:subClassOf semapi:BaseClass .
instances:Instance1 a semapi:DerivedClass;
semapi:hasChainTo (
[
a semapi:DerivedClass;
semapi:hasChainTo (
[C1]
[C2]
)
]
)
If semapi:hasChainTo rdfs:range semapi:BaseClass
then it implies the list is rdf:type semapi:BaseClass
.
What I really mean to say is each item in the list is rdf:type
(ei. [C1] rdf:type semapi:BaseClass
, [C2] rdf:type semapi:BaseClass
, ...)
How can I do this? Do I need Owl (preferably not)?
Depending on how you want to do this, you have a few options. I think you're trying to stick to non-OWL reasoning, so we'll make sure to include such a solution, but I do want to touch on an OWL solution too, since for some similar situations, it works very well.
Using OWL and a custom ObjectList
If you do have the option of using an OWL reasoner, then this is a nice case in which you can create your own list vocabulary and use some property chains. The idea is that you introduce a class List
with an individual nil
, and properties first
and rest
. You're really just copying the vocabulary in your own namespace. Then lets say you define two properties
likes
: relates an individual X to another individual Y; "X likes Y".
likesList
: relates an individual X to a List (not an RDF list, though) of individuals that X likes.
Then you can introduce two property chain axioms
likesList subPropertyChain likesList o rest
: if X likesList (_ ...), then X likesList (...).
This way, from X likes (A B C)
we get X likes (A B C)
, X likes (B C)
, X likes (C)
, and X likes nil
.
likes subPropertyChain likesList o first
: if X likesList (A ...), then X likes A.
Then, from all those inferred statements above, we get X likes A
, X likes B
, and X likes C
.
In Turtle, this looks like:
@prefix : <http://www.example.org/distributing#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
<http://www.example.org/distributing>
a owl:Ontology .
:List
a owl:Class .
:nil a :List , owl:NamedIndividual .
:first
a owl:ObjectProperty .
:rest
a owl:ObjectProperty .
:likes
a owl:ObjectProperty ;
owl:propertyChainAxiom
(:likesList :first) .
[] a owl:Axiom ;
rdfs:comment "If X likesList (A ...), then X likes A." ;
owl:annotatedProperty
owl:propertyChainAxiom ;
owl:annotatedSource :likes ;
owl:annotatedTarget (:likesList :first) .
:likesList
a owl:ObjectProperty ;
rdfs:comment "Relates an individual I1 to a ObjectList of individuals that I1 likes." ;
owl:propertyChainAxiom
(:likesList :rest) .
[] a owl:Axiom ;
rdfs:comment "If X likesList (A B C), then since (B C) is the rest of (A B C), X likesList (B C), too." ;
owl:annotatedProperty
owl:propertyChainAxiom ;
owl:annotatedSource :likesList ;
owl:annotatedTarget (:likesList :rest) .
This gets a bit inconvenient if you have to write the RDF manually, since you have to do
X :likesList [ :first A ;
:rest [ :first B ;
:rest [ :first C ;
:rest nil ] ] ] .
and can't use the nice (...)
syntax that Turtle includes. This also really doesn't help for the case that you've got, since OWL classes aren't individuals, so they can't be the object of object properties, and rdf:type
isn't an object property. I just wanted to include this because it's a nice way for an object property to distribute over a (non-RDF) list of individuals, and because the approach makes the following solutions clearer.
Using SPARQL queries
Given data like:
@prefix : <urn:ex:> .
:X :pList (:A :B :C :D) .
A SPARQL query like
prefix : <http://example.org/>
prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
construct {
?x :p ?y
}
where {
?x :pList/rdf:rest*/rdf:first ?y
}
produces
@prefix : <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
:X :p :A ;
:p :C ;
:p :B ;
:p :D .
In imitation of the the OWL based approach above, I used two properties pList
and p
, but they could be the same, in which case p
would be "distributed" over the list.
With a datastore somewhere, you should be able to do a SPARQL update using insert/where
:
prefix : <http://example.org/>
prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
insert {
?x :p ?y
}
where {
?x :pList/rdf:rest*/rdf:first ?y
}
to add the data to the store.
Using a Prolog like syntax
If you want to actually get this reasoning to be performed with a reasoner, you'll be in the domain of reasoner specific stuff. However, lots of reasoners support a Prolog-like query language, and you can write these rules there, too. I don't know AllegoGraph's RDFS++ syntax, but the general structure would include some definitions like:
?x p ?y :- ?x pList ?list, ?list rdf:first ?y
?x pList ?l :- ?x pList ?list, ?list rdf:rest ?l