Re: issue-95 metamodel simplifications

 A modest proposal for a revamped SHACL syntax

This proposal rearranges the SHACL constructs, collapsing constraints and
shapes into one construct.  The result is a more regular SHACL syntax with a
simpler metamodel.  A few constructs become a bit more verbose.


The main SHACL construct is a shape (sh:Shape).  Shapes have zero or more
scopes (triples with the shape as subject and sh:scopeNode, sh:scopeClass,
sh:scopePropertyObject, or sh:scopePropertySubject as the predicate), zero
or more filters (values of sh:filter) which are themselves shapes, one or
more components (triples with the shape as subject and one of the component
properties as property, including associated triples as necessary), and can
be closed (sh:closed value true, zero or more fillers of

Note:  The shape components might be called constraints.  I didn't use
constraint so as not to cause confusion with the current constraints in SHACL.

The syntax of the various components are:
sh:class class
... (various other simple components)
sh:pattern pattern
Q: How to handle flags?  There are several options.
sh:equals ( property/path ... property/path )
sh:disjoint ( property/path ... property/path )
sh:lessThan ( property/path ... property/path )
... (other comparison components)
sh:fillers [ sh:property property/path; sh:shape shape ]
sh:list shape
sh:shape shape
sh:and ( shape ... shape )
sh:or ( shape ... shape )
sh:not shape
sh:minCard int
sh:maxCard int
sh:uniqueLang true
sh:partition ( shape_1, ..., shape_n )

A property/path is either a property or a list consisting of nodes linked to
properties via sh:property or sh:propertyInverse.  (Yes, this is a bit

Note:  Qualified cardinalities are replaced by an embedded shape where the
embedded shape's filter has the same role as the filler for

A vital aspect of this syntax is that each component of shapes uses exactly
one triple with the shape as subject.  The sole exception is closure, and
closure could be reworked this way as well, but closure is special in the
semantics so it is not so bad to make it special in the syntax as well.


sh:personShape [ a sh:Shape;
 sh:scopeClass ex:Person ;
 sh:fillers [ sh:path name; sh:shape [ a sh:Shape; sh:datatype xs:string ] ] ;
 sh:fillers [ sh:path child; sh:shape [ a sh:Shape; sh:class ex:Person ] ] ;
 sh:fillers [ sh:path age; sh:shape [ a sh:Shape; sh:datatype xs:integer;
                                      sh:minCount 1 ; sh:maxCount 1 ] ] ] .

sh:personShape is satisfied on a graph if all instances of ex:Person have
all their stated names be strings, all their stated children belonging to
ex:Person, and have exactly one stated age, which is an integer.

Note: Combining the path and the shape would shorten the syntax but does
complicate the metamodel.  Alternatively sh:fillers coud take a two-element

sh:SJG [ a sh:Shape;
 sh:scopeClass ex:Person ;
 sh:filter [ a sh:Shape ;
   sh:fillers [ sh:path gender;
           sh:shape [ a sh:Shape; sh:in ( ex:female ) ] ]  ];
 sh:filter [ a sh:Shape ;
   sh:fillers [ sh:path ( [ sh:property child ] [ sh:property child ] ) ;
           sh:shape [ a sh:Shape; sh:minCount 1 ] ] ] ;
 sh:fillers [
   sh:path child ;
   [ a sh:Shape ;
     sh:filter [ a sh:Shape ;
               sh:fillers [ sh:path gender;
          sh:shape [ a sh:Shape; sh:in ( ex:male ) ] ] ] ;
     sh:class ex:Professional ] ] ] .

sh:SJG is satisfied on a graph if all instances of ex:Person (the scope) and
have ex:female as gender (the first filter) and have at least one grandchild
(the second filter) have all their male children be instances of

Semantics (ignoring recursion)

A graph satisfies a shape if the set of nodes of the graph selected by any
scope of the shape satisfies the shape.  A sh:scopeNode filler selects that
node.  A sh:scopeClass filler selects each node in the graph that is an
instance of the class.  A sh:scopePropertyObject filler selects each node in
the graph that is an object for that property.  A sh:scopePropertySubject
selects each node in the graph that is a subject for that property.

A shape satisfies a set of nodes (the input nodes) as follows.  The input
nodes that satisfy each of the filters of the shape are called in-filter
nodes, those that do not are out-of-filter nodes.  Some components (those
involving sh:shape, sh:and, sh:or, sh:not, sh:minCard, sh:maxCard,
sh:uniqueLang, and sh:partition) work on the set of in-filter nodes as a
whole.  If a component of this kind is not satisfied then each of the
in-filter nodes fails to satisfy the shape.  Some components (values of
sh:class, ..., sh:pattern, sh:fillers) work on each in-filter node
independently.  Each in-filter node that fails to satisfy one or more of
these components fails to satisfy the shape.  If the shape is closed an
in-filter node fails to satisfy the shape if it has a filler for some
property that is neither the path of some component of the shape nor a
filler for sh:ignoredProperty.  Each in-filter node that does not fail to
satisfy the shape is said to satisfy the shape.  A shape is satisfied on a
set of input nodes if there are no in-filter nodes that fail to satisfy the
shape on these input nodes.

The components work as follows:

sh:class class - the node belongs to class
sh:pattern pattern - the "name" of the node matches pattern
sh:equals ( path ... )
 - the node has the same fillers for each path
sh:disjoint ( path ... )
 - the fillers for the paths are pairwise disjoint
sh:lessThan ( path ... )
 - the fillers for a path are smaller than the fillers for the next path
sh:fillers [ sh:path path; sh:shape shape ]
 - the fillers of path for the node satisfy each shape
sh:list shape
 - the nodes in the list are the transitive-reflexive closure of rdf:rest
 - each such node has a single filler for rdf:rest, except rdf:nil which has none
 - each such node has a single filler for rdf:first, except rdf:nil which has none
 - the nodes that are rdf:first fillers

sh:shape shape
 - the set of in-filter nodes satisfies the shape
sh:and ( shape ... shape )
 - the set of in-filter nodes satisfies each shape
sh:or ( shape ... shape )
 - each in-filter node is individually satisfied by some shape, i.e., if
   there are only sh:class constructs then each in-filter node belongs to one
   of them or
   the set of in-filter nodes satisfies some shape, i.e., if there are n
   fillers and a sh:minCard of n then the sh:or is satisfied
sh:not shape
 - the set of in-filter nodes does not satisfy the shape
sh:minCard int - there are at least int in-filter nodes
sh:maxCard int - there are at most int in-filter nodes
sh:uniqueLang true - only one in-filter node for any particular language tag
sh:partition ( shape_1, ..., shape_n )
 - let input_1 be the set of in-filter nodes
 - let input_i+1 be the out-of-filter nodes of shape_i on input_i
 - shape_i is satisfied on input_i, for 1<=i<=n
 - input_n+1 is empty

Meta model classes and properties

 - sh:Shape
 - sh:scopeNode, sh:scopeClass, sh:scopePropertyObject, sh:scopePropertySubject
 - sh:filter, sh:closed, sh:ignoredProperty
 - sh:class, ..., sh:pattern, sh:equals, sh:disjoint, sh:lessThan, ...,
   sh:fillers, sh:list
 - sh:shape, sh:and, sh:or, sh:not, sh::minCard, sh:maxCard,
   sh:uniqueLang, sh:partition
 - sh:property, sh:propertyInverse
 - sh:severity, sh:name, ...

Received on Thursday, 3 March 2016 00:13:50 UTC