Re: Can Shapes always be Classes?

* Holger Knublauch <holger@topquadrant.com> [2014-11-06 09:38+1000]
> I think it's encouraging to read suggestions on how we could merge
> ideas from the various proposals, e.g. extend SPIN to make the
> scenario below easier to represent. This is always a possibility.
> 
> Thanks for providing a specific example, which makes our discussion
> more focused. I do believe that the example below can be expressed
> with the existing SPIN spec via something like
> 
> :Issue
>     spin:constraint [
>         a sp:Ask ;
>         sp:text """
>             # The assignee must have an mbox
>             ASK {
>                 ?this :assignedTo ?assignee .
>                 FILTER NOT EXIST { ?assignee foaf:mbox ?any }

multiplied out for cardinality over :submittedBy:{given,family},
status=unassigned | (status=assigned &&
assignedTo/{givenName,familyName,mbox}), etc. gave me the 107 lines of
SPARQL at the bottom of this message.


>             }
>             """
>     ]
> 
> Basically this associates the constraint to the starting point and
> uses a path to walk into the Person.
> 
> The question then becomes how acceptable is that solution compared
> to having to introduce a special mechanism and change the whole
> execution engine, introduce the notion of starting nodes etc. I
> would say the case you describe is quite rare and therefore people
> should be able to live with the little inconvenience.

Any time you see a restriction in OWL you have an example of a
contextual constraint. OWL literature pretty much indoctrinates for
constraining general predicates for us in particular classes, e.g.
the pizza tutorial's :hasTopping. I've used many nested property
restrictions in the projects that I've worked on.


>                                                       The benefit of
> the SPIN solution above is consistency, and users just need to
> understand the simple principle of "object-oriented attachment" vs
> context-sensitive execution and regular expressions.
> 
> Furthermore, if the above is a recurring pattern, then it could be
> generalized into a SPIN template, producing a definition such as
> 
> :Issue
>     spin:constraint [
>         a ex:RequiredPropertyInContext ;
>         arg:contextProperty :assignedTo ;
>         arg:requiredProperty foaf:mbox
>     ]

Note that Resource Shapes does that but keeps the nested constraints
in a separate shape:

[[
<http://ex.example/x#NewIssueShape> a rs:ResourceShape ;
...
    rs:property [
        rs:name "submittedBy" ;
        rs:propertyDefinition :submittedBy ;
        rs:valueShape x:SubmitterShape ;
        rs:occurs rs:Exactly-one ;
    ] ;
...

<http://ex.example/x#SubmitterShape> a rs:ResourceShape ;
    rs:property [
        rs:name "givenName" ;
        rs:propertyDefinition foaf:givenName ;
        rs:valueType shex:Literal ;
        rs:occurs rs:Exactly-one ;
    ] ;
    rs:property [
        rs:name "familyName" ;
        rs:propertyDefinition foaf:familyName ;
        rs:valueType shex:Literal ;
        rs:occurs rs:Zero-or-one ;
    ] ;
 .
]]


> Holger
> 
> 
> On 11/6/2014 4:25, Eric Prud'hommeaux wrote:
> >* Holger Knublauch <holger@topquadrant.com> [2014-11-05 15:35+1000]
> >>On 11/5/2014 15:26, Irene Polikoff wrote:
> >>>>From: Holger Knublauch [mailto:holger@topquadrant.com]
> >>>>Sent: Wednesday, November 05, 2014 12:16 AM
> >>>>To: public-data-shapes-wg@w3.org
> >>>>Subject: Can Shapes always be Classes?
> >>>>
> >>>>I believe there is a fundamental difference in how the various proposals
> >>>>treat the relationship between resources and their shapes:
> >>>>
> >>>>- In OWL and SPIN, constraints are attached to classes. rdf:type triples are
> >>>>used to determine which constraints need to be evaluated for a given
> >>>>instance.
> >>>>
> >>>>- In the original Resource Shapes and ShEx, Shapes are stand-alone entities
> >>>>that may or may not be associated with a class. Other mechanisms than
> >>>>rdf:type are used to point from instances to their shapes.
> >>>>
> >>>>I believe the main motivation for the latter design are the User Stories
> >>>>S7 and S8: different shapes at different times, and properties can change as
> >>>>they pass through the workflow. I would like to learn more about this and
> >>>>have specific examples that we can evaluate.
> >>>>
> >>>>My current assumption is that these scenarios can be expressed via named
> >>>>graphs, so that different class definitions are used in different contexts.
> >>>>Which graph to use would be specified in some kind of header metadata or via
> >>>>a special property (e.g. owl:imports). Alternatively, different classes
> >>>>could be used, just like different shapes are used depending on the context.
> >>>>I argue that using rdf:type and RDFS classes is a well-established mechanism
> >>>>that we should try to build upon. What problems do the proponents of the
> >>>>decoupling see with those ideas?
> >I think the fundamental issue is whether these are effectively
> >context-sensitive grammars. As currently proposed, SPIN depends on
> >type annotations attached to the data. It would be possible to add a
> >step which creates a premise when validating some node. I believe this
> >would get around all of the issues stemming from requiring fully
> >discriminating types.
> >
> >Use Case: context-sensitive-rooted-issue-interface
> >
> >An LDP service accepts new Issues. A posted issue is expected to have
> >a :name, :status and a :submittedBy. If the status is :assigned, it
> >must have an :assignedTo . It may also have references to other Issues
> >which may or may not be in the system so they are referenced by :name.
> >
> >Sample Data:
> >   _:IssueA a :Issue ;
> >     :name        "funny smell and no lights" ;
> >     :status      :assigned ;
> >     :submittedBy _:Bob ;
> >     :assignedTo  _:Bob ;
> >     :related     [ a :Issue ; :name "smoke coming from unit" ],
> >                  [ a :Issue ; :name "110V capacitor in French unit" ] .
> >
> >   _:Bob    a foaf:Person ;
> >     foaf:givenName "Bob" ;
> >     foaf:familyName "Smith" ;
> >     foaf:mbox    <mailto:bob@example.com> .
> >
> >There are multiple nodes of type :Issue so the client can specify the
> >start node as _:IssueA (e.g. in a header). This makes the posted data
> >a "pointed graph".
> >
> >If the requirements for the :submittedBy and the :assignedTo are
> >different, we have need context-sensitivity.
> >
> >   x:NewIssueShape {
> >     :name LITERAL,
> >     :submittedBy @x:SubmitterShape,
> >     (:status (:unassigned :unknown)
> >      | :status (:assigned),
> >        :assignedTo @x:AssigneeShape),
> >     :related { :name LITERAL }*
> >   }
> >
> >   x:SubmitterShape {
> >     foaf:givenName LITERAL,
> >     foaf:familyName LITERAL?
> >   }
> >
> >   x:AssigneeShape {
> >     foaf:givenName LITERAL,
> >     foaf:familyName LITERAL,
> >     foaf:mbox IRI
> >   }
> >
> >If we have some OWL like (eliding cardinalities):
> >
> >   Class: x:NewIssueShape
> >     SubClassOf:
> >       :name some rdfs:Literal,
> >       :submittedBy some x:SubmitterShape,
> >       (:status value :unassigned
> >        or
> >        (:status value :assigned and :assignedTo some x:AssigneeShape))
> >       :related (:name rdfs:Literal)
> >
> >   Class: x:SubmitterShape
> >     SubClassOf:
> >       foaf:givenName some rdfs:Literal,
> >       foaf:familyName some rdfs:Literal
> >
> >   Class: x:AssigneeShape
> >     SubClassOf:
> >       foaf:givenName some rdfs:Literal,
> >       foaf:familyName some rdfs:Literal,
> >       foaf:mboxName some rdf:Resource
> >
> >, we can validate the data with the premise _:IssueA a x:NewIssueShape.
> >The validation will recursively test the nested constraints. This of
> >course hinges on being able to verify a premise.
> >
> >It seems reasonable to extend SPIN to test premises. It could use the
> >same idea where instead of an rdfs:range to specify an expected object
> >type, one could use Resource Shapes' rs:valueShape. This would assert
> >the premise that e.g. _:Bob a x:SubmitterShape and then another
> >_:Bob a x:AssigneeShape .
> >
> >
> >>>>I think this is a major design decision that we need to clarify early.
> >>>>Instead of excluding those scenarios, I would like to accommodate them
> >>>>without having to introduce completely new mechanisms.
> >>>>
> >>>Holger,
> >>>
> >>>I believe you are saying that there could be two (or more) named graphs each
> >>>containing different sets of constraints for a particular classes (or
> >>>classes). For example:
> >>>
> >>>Graph A: contains rdf:type statements for a set of classes and properties.
> >>>Can also contain other RDFS or OWL axioms
> >>>
> >>>Graph B: contains some constraints for classes declared in Graph A
> >>>
> >>>Graph C: contains a different set of constraints for classes declared in
> >>>Graph A
> >>>
> >>>And so on
> >>>
> >>>A given application can then chose what set of constraints it will be using
> >>>- Graph B or Graph C.
> >>>
> >>>Is this correct?
> >>Yes sorry I was brief. Let's take an extreme use case, where the
> >>same ex:Instance must fulfill different constraints in scenario A
> >>and B.
> >>
> >>     ex:Instance
> >>         a ex:Person ;
> >>         foaf:firstName "John" .
> >>
> >>Scenario A: Each Person can have any number of first names.
> >>
> >>Scenario B: Each Person must have exactly one first name.
> >>
> >>In scenario A, it would have
> >>
> >>     <instance graph> owl:imports <schema A>
> >>
> >>where <schema A> is simply the unconstrained class definition.
> >>
> >>     ex:Person a rdfs:Class .
> >>
> >>In scenario B, it would owl:import <schema B> which is
> >>
> >>     ex:Person a rdfs:Class ;
> >>         constraint foaf:firstName exactly 1 .
> >>
> >>I hope this explains the named graph work-around.
> >>
> >>Holger
> 
> 

[[
PREFIX :<http://ex.example/#>
PREFIX foaf:<http://foaf.example/#>
PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX x:<http://ex.example/x#>
PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
ASK {
    { SELECT ?http://ex.example/x#NewIssueShape {
      ?http://ex.example/x#NewIssueShape :name ?o .
    } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
    { SELECT ?http://ex.example/x#NewIssueShape {
      ?http://ex.example/x#NewIssueShape :name ?o . FILTER (isLiteral(?o))
    } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
    { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c0) {
      ?http://ex.example/x#NewIssueShape :submittedBy ?o .
    } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
    { SELECT ?http://ex.example/x#NewIssueShape {
      ?http://ex.example/x#NewIssueShape :submittedBy ?o . FILTER ((isIRI(?o) || isBlank(?o)))
    } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
    { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c1) {
        { SELECT ?http://ex.example/x#NewIssueShape ?http://ex.example/x#SubmitterShape {
          ?http://ex.example/x#NewIssueShape :submittedBy ?http://ex.example/x#SubmitterShape . FILTER (true && (isIRI(?http://ex.example/x#SubmitterShape) || isBlank(?http://ex.example/x#SubmitterShape)))
        } }
        { SELECT ?http://ex.example/x#SubmitterShape {
          ?http://ex.example/x#SubmitterShape foaf:givenName ?o .
        } GROUP BY ?http://ex.example/x#SubmitterShape HAVING (COUNT(*)=1)}
        { SELECT ?http://ex.example/x#SubmitterShape {
          ?http://ex.example/x#SubmitterShape foaf:givenName ?o . FILTER (isLiteral(?o))
        } GROUP BY ?http://ex.example/x#SubmitterShape HAVING (COUNT(*)=1)}
        { SELECT ?http://ex.example/x#SubmitterShape (COUNT(*) AS ?http://ex.example/x#SubmitterShape_c0) {
          ?http://ex.example/x#SubmitterShape foaf:familyName ?o .
        } GROUP BY ?http://ex.example/x#SubmitterShape HAVING (COUNT(*)<=1)}
        { SELECT ?http://ex.example/x#SubmitterShape (COUNT(*) AS ?http://ex.example/x#SubmitterShape_c1) {
          ?http://ex.example/x#SubmitterShape foaf:familyName ?o . FILTER (isLiteral(?o))
        } GROUP BY ?http://ex.example/x#SubmitterShape HAVING (COUNT(*)<=1)}
        FILTER (?http://ex.example/x#SubmitterShape_c0 = ?http://ex.example/x#SubmitterShape_c1)
    } GROUP BY ?http://ex.example/x#NewIssueShape }
    FILTER (?http://ex.example/x#NewIssueShape_c0 = ?http://ex.example/x#NewIssueShape_c1)
    OPTIONAL { ?http://ex.example/x#NewIssueShape :submittedBy ?http://ex.example/x#NewIssueShape_http://ex.example/x#SubmitterShape_ref0 . FILTER (true && (isIRI(?http://ex.example/x#NewIssueShape_http://ex.example/x#SubmitterShape_ref0) || isBlank(?http://ex.example/x#NewIssueShape_http://ex.example/x#SubmitterShape_ref0))) }
    { SELECT ?http://ex.example/x#NewIssueShape WHERE {
        {
            { SELECT ?http://ex.example/x#NewIssueShape {
              ?http://ex.example/x#NewIssueShape :status ?o .
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
            { SELECT ?http://ex.example/x#NewIssueShape {
              ?http://ex.example/x#NewIssueShape :status ?o . FILTER ((?o = :unassigned || ?o = :unknown))
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
        } UNION {
            { SELECT ?http://ex.example/x#NewIssueShape {
              ?http://ex.example/x#NewIssueShape :status ?o .
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
            { SELECT ?http://ex.example/x#NewIssueShape {
              ?http://ex.example/x#NewIssueShape :status ?o . FILTER ((?o = :assigned))
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
            { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c2) {
              ?http://ex.example/x#NewIssueShape :assignedTo ?o .
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
            { SELECT ?http://ex.example/x#NewIssueShape {
              ?http://ex.example/x#NewIssueShape :assignedTo ?o . FILTER ((isIRI(?o) || isBlank(?o)))
            } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*)=1)}
            { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c3) {
                { SELECT ?http://ex.example/x#NewIssueShape ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#NewIssueShape :assignedTo ?http://ex.example/x#AssigneeShape . FILTER (true && (isIRI(?http://ex.example/x#AssigneeShape) || isBlank(?http://ex.example/x#AssigneeShape)))
                } }
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:givenName ?o .
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:givenName ?o . FILTER (isLiteral(?o))
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:familyName ?o .
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:familyName ?o . FILTER (isLiteral(?o))
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:mbox ?o .
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
                { SELECT ?http://ex.example/x#AssigneeShape {
                  ?http://ex.example/x#AssigneeShape foaf:mbox ?o . FILTER (isIRI(?o))
                } GROUP BY ?http://ex.example/x#AssigneeShape HAVING (COUNT(*)=1)}
            } GROUP BY ?http://ex.example/x#NewIssueShape }
            FILTER (?http://ex.example/x#NewIssueShape_c2 = ?http://ex.example/x#NewIssueShape_c3)
            OPTIONAL { ?http://ex.example/x#NewIssueShape :assignedTo ?http://ex.example/x#NewIssueShape_http://ex.example/x#AssigneeShape_ref0 . FILTER (true && (isIRI(?http://ex.example/x#NewIssueShape_http://ex.example/x#AssigneeShape_ref0) || isBlank(?http://ex.example/x#NewIssueShape_http://ex.example/x#AssigneeShape_ref0))) }
        }
    } GROUP BY ?http://ex.example/x#NewIssueShape HAVING (COUNT(*) = 1)}
    { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c4) {
      ?http://ex.example/x#NewIssueShape :related ?o .
    } GROUP BY ?http://ex.example/x#NewIssueShape}
    { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c5) {
      ?http://ex.example/x#NewIssueShape :related ?o . FILTER ((isIRI(?o) || isBlank(?o)))
    } GROUP BY ?http://ex.example/x#NewIssueShape}
    FILTER (?http://ex.example/x#NewIssueShape_c4 = ?http://ex.example/x#NewIssueShape_c5)
    { SELECT ?http://ex.example/x#NewIssueShape (COUNT(*) AS ?http://ex.example/x#NewIssueShape_c6) {
        { SELECT ?http://ex.example/x#NewIssueShape ?4 {
          ?http://ex.example/x#NewIssueShape :related ?4 . FILTER (true && (isIRI(?4) || isBlank(?4)))
        } }
        { SELECT ?4 {
          ?4 :name ?o .
        } GROUP BY ?4 HAVING (COUNT(*)=1)}
        { SELECT ?4 {
          ?4 :name ?o . FILTER (isLiteral(?o))
        } GROUP BY ?4 HAVING (COUNT(*)=1)}
    } GROUP BY ?http://ex.example/x#NewIssueShape }
    FILTER (?http://ex.example/x#NewIssueShape_c4 = ?http://ex.example/x#NewIssueShape_c6)
    OPTIONAL { ?http://ex.example/x#NewIssueShape :related ?http://ex.example/x#NewIssueShape_4_ref0 . FILTER (true && (isIRI(?http://ex.example/x#NewIssueShape_4_ref0) || isBlank(?http://ex.example/x#NewIssueShape_4_ref0))) }
}
]]

-- 
-ericP

office: +1.617.599.3509
mobile: +33.6.80.80.35.59

(eric@w3.org)
Feel free to forward this message to any list for any purpose other than
email address distribution.

There are subtle nuances encoded in font variation and clever layout
which can only be seen by printing this message on high-clay paper.

Received on Tuesday, 18 November 2014 23:47:42 UTC