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:zeroOrMorePath 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.