RDF lists are a rather weak spot of the semantic technology stack. Their design is basically a linked structure of intermediate list nodes using dedicated properties rdf:rest and rdf:first. So a simple list of string literals “red”, “yellow” and “green” is internally represented using
ex:MyTrafficLight a ex:TrafficLight ; ex:colors [ rdf:first "red" ; rdf:rest [ rdf:first "yellow" ; rdf:rest [ rdf:first "green" ; rdf:rest rdf:nil ; ] ] ] .
Notations such as Turtle provide syntactic sugar to hide this internal complexity, so that the snippet above can be written as
ex:MyTrafficLight a ex:TrafficLight ; ex:colors ( "red" "yellow" "green" ) .
Now assume we want to define constraints on the actual members of such a list. For example, we may want to state:
- The members of the ex:colors list must be strings
- These strings must have a minimum length of 1 character
- There must be between two and three members in the list
Neither RDF nor OWL provide sufficient means to express such constraints. We can state
ex:colors a rdf:Property ; rdfs:domain ex:TrafficLight ; rdfs:range rdf:List .
but that's about all we can do in RDF Schema. Using W3C's new Shapes Constraint Language (SHACL), we have much more expressive power. It's still a bit geeky but gets the job done:
ex:TrafficLightShape a sh:NodeShape ; sh:targetClass ex:TrafficLight ; sh:property [ sh:path ex:colors ; sh:node dash:ListShape ; sh:property [ sh:path ( [ sh:zeroOrMore rdf:rest ] rdf:first ) ; sh:datatype xsd:string ; sh:minLength 1 ; sh:minCount 2 ; sh:maxCount 3 ; ] ] .
Let's disassemble this example a bit. The SHACL shape ex:TrafficLightShape applies to all instances of the class ex:TrafficLight (as specified using sh:targetClass). The shape has a property constraint on the property ex:colors. All values of this property must conform to the shape called dash:ListShape (which is part of the DataShapes standard library). The dash:ListShape shape provides reusable logic to recursively walk the rdf:List and verify that each node has exactly one rdf:first and exactly one rdf:rest value. You can simply reuse that shape by owl:importing the http://datashapes.org/dash namespace.
Now the interesting bit: the property shape on ex:colors has another nested property constraint, which applies to all values of ex:colors. The sh:path of that property shape is equivalent to the SPARQL property path rdf:rest*/rdf:first, which is delivering all members of a given list. Since property shapes apply to the set of all so-called value nodes (here: the members of the rdf:List), we can use the standard constraint properties sh:datatype, sh:minCount, sh:maxCount and sh:minLength to define constraint that apply to all list members. Needless to say, any other SHACL constraint type could be used here as well, e.g. sh:pattern to specify a regular expression that the list members need to match.