2019, March 24th

Catherine Faron Zucker <faron@i3s.unice.fr>

Fabien Gandon <fabien.gandon@inria.fr>

This document defines a function definition language on top of SPARQL filter language. It enables users to define and use simple extension functions directly in (extended) SPARQL. The body of functions is written using SPARQL filter language augmented with additional statements.

1.1 Relationship to W3C Recommendations

2 Function Definition

2.1 Function

2.2 Lambda Expression

2.3 Annotation

3 Statement

3.1 SPARQL

3.2 Let

3.3 For

3.4 Pattern Matching

3.5 If Then Else

3.6 Return

3.7 Error

4 Second Order Function

4.1 Funcall

4.2 Apply

4.3 Map

4.4 Reduce

5 Predefined Extension Function

6 Datatype

6.1 LDScript Datatype

6.2 RDF Datatype

6.3 SPARQL Datatype

7 Language Syntax

8 SPARQL Extension

8.1 LDScript in SPARQL

8.2 Aggregate

8.3 Values Unnest

8.4 Property Path Variable

8.5 Named Graph Pattern

9 Use Case

9.1 Functional Property

9.2 Functional Service

9.3 Approximate Match

9.4 Recursive Match

9.5 Event Driven Function

9.6 Predefined Query

10 Implementation

11 Conclusion

In addition to the existing standards dedicated to representation or querying, Semantic Web programmers could really benefit from a dedicated programming language enabling them to directly define functions on RDF terms, RDF graphs or SPARQL results. This is especially the case, for instance, when defining SPARQL extension functions. We propose a function definition language on top of SPARQL filter language by introducing a function clause. It enables users to define and use simple extension functions directly in (extended) SPARQL. The body of functions is written using SPARQL filter language augmented with additional statements. The language can be seen as a kind of SPARQLScript w.r.t SPARQL in the spirit of JavaScript w.r.t HTML.

The example below defines and uses the factorial function. Function definitions occur just after query definition.

select * where { ?x rdf:value ?n filter (?n >= us:fac(10)) } function us:fac(?n) { if (?n = 0, 1, ?n * us:fac(?n - 1)) }

This proposition is strongly related to SPARQL 1.1 Query Language and to RDF 1.1 Concepts and Abstract Syntax.

The language is built on top of SPARQL filter language extended with the statements defined in this proposition. The objects of the language are SPARQL variables and RDF terms: URI, literal, blank node. The language objects can also be RDF triple and graph as well as SPARQL query solution (mapping) and solution sequence (mappings). A list datatype is also introduced whose elements can be any of the objects listed above, including lists. List elements do not need to be of the same kind or of the same datatype. Triple, graph, mapping, mappings and list are managed as RDF literals with extension datatypes: dt:triple, dt:graph, dt:mapping, dt:mappings and dt:list. Their content is accessed by pattern matching and they are iterable. We call LDScript terms the union of RDF terms and LDScript literals with extension datatype in the dt: namespace.

In the document, we use prefix and namespaces shown below:

prefix rq: <http://ns.inria.fr/sparql-function/> prefix dt: <http://ns.inria.fr/sparql-datatype/> prefix st: <http://ns.inria.fr/sparql-template/> prefix xt: <http://ns.inria.fr/sparql-extension/> prefix us: <http://ns.inria.fr/sparql-extension/user/>

This statement defines a function that can be used in a query, a rule, a template or another function. The name of a function is an URI, it can have zero, one or several arguments that are variables in SPARQL syntax. Function overloading is provided: several functions can be defined with the same name and different number of arguments. Functions can call other LDScript functions including themselves, SPARQL functions or extension functions. The body is a sequence of expressions. The result of the function is the result of the last expression of the body, or the result of the return function if any.

function us:fun(?x, ?y) { ?x + ?y }

This statement defines an anonymous function that can be used with second order functions such as: apply, funcall, map and reduce. As it is an expression of the language, it can be bound to a variable, passed to a function call as parameter, it can be an element of a list, etc. At compile time, compiling a lambda expression generates a function definition with a generated URI. At runtime, a lambda expression definition evaluates to the URI of the generated function, hence lambda expression definition is transparent for the interpreter as it resumes to its URI.

lambda(?x) { 1 / (?x * ?x) }

maplist(lambda(?x) { 1 / (?x * ?x) } , xt:iota(5))

This @public annotation exports function definitions in the SPARQL interpreter in such a way that future SPARQL queries can use them within current runtime session.

@public function us:foo() { xt:display("Hello World") } @public { function us:bar(?x, ?y) { us:gee(?x * ?y) } function us:gee(?x) { ?x * ?x } }

This section details LDScript statements.

LSDcript inherits SPARQL Filter language statements, including the exists clause, SPARQL select and construct queries and Update queries. These statements are evaluated with the Dataset of the embedding SPARQL query that runs the LDScript function. For syntactic reasons, SPARQL queries are embedded in a query statement, except in the let and for statements where it can be avoided.

query(select ?x ?y where { ?x foaf:knows ?y })

The result of a select (resp. construct) query is a dt:mappings (resp. dt:graph) datatype extension literal. These datatypes act as "pointers" to the underlying data structure that implements the result of the query.

datatype(query(select ?x ?y where {?x foaf:knows ?y})) = dt:mappings datatype(query(construct where {?x foaf:knows ?y})) = dt:graph

At runtime, variables projected in the select clause of a query that are bound in LDScript stack are bound in the where clause. They are bound using an extended values clause that is generated dynamically. It is extended because it accepts blank node values in addition to URI and literals. The query above is evaluated as shown below if the value of ?x is v in the stack.

function us:foo(?x) { query(select ?x ?y where { values ?x { v } ?x foaf:knows ?y }) }

For construct queries, variables that are in-scope in the where clause and that are bound in LDScript stack are bound in the where clause using an extended values clause.

For the exists { BGP } clause, variables that are in-scope in the BGP and that are bound in LDScript stack are bound in the BGP using an extended values clause.

Statements such as if, bound, coalesce are also available. SPARQL functions are also available with the rq: prefix.

It is worth noticing that, as any statement, a SPARQL query can be embedded in a lambda expression.

let (?query = lambda() { query(select .. where ..) }) { datatype(funcall(?query)) = dt:mappings }

This statement defines local variables whose scope is the body of the let statement. The result of the statement is the result of the last expression of the body, or the result of the return statement if any.

let (?z = ?x + ?y, ?t = 2 * ?z) { us:foo(?t) }

The let statement enables users to map list elements to variables. The number of variables may be less than the size of the list.

let ((?x, ?y, ?z) = ?list) { us:foo(?x, ?y, ?z) }

The left argument can be a list of lists of variables.

let (((?a, ?b), (?c, ?d)) = @((1 2)(3 4)) { us:foo(?a, ?b) ; us:foo(?c, ?d) }

The let statement can have a select-where query as argument. In this case, the variables in the select clause are defined and bound, in the body of the let clause, with the values of the first query solution. If a variable has no value in the first solution (e.g. due to an optional), the body is executed with the variable left unbound. These cases can be trapped in the body by the bound or coalesce functions.

let (select ?x ?y where { ?x foaf:knows ?y }) { us:bar(?x, ?y) }

If the left argument is a list of variables, each variable is bound to the corresponding query solution (a mapping) in order.

let ((?s1, ?s2) = select * where { ?x foaf:knows ?y }) { us:foo(?s1, ?s2) }

If the left argument is a list of list of variables, each variable is bound to the value of the corresponding variable (with same name) in the first query solution.

let (((?x, ?y)) = select * where { ?x foaf:knows ?y }) { us:foo(?x, ?y) }

The let statement can take as second argument a construct-where query. The value of the variable is the RDF result graph.

function us:foo(?x) { let (?g = construct where { ?x foaf:knows ?y}) { ?g } }

Variables in-scope in the where clause that are bound in LDScript stack are bound in the where clause using an extended values clause generated at runtime. The query above is evaluated as shown below if the value of ?x is v in the stack.

function us:foo(?x) { let (?g = construct where { values ?x { v } ?x foaf:knows ?y}) { ?g } }

This statement assigns a (new) value to a variable.

set (var name = exp e)

set (?x = ?x + 1)

Local variables are defined by let (var = exp), for (var in exp), function us:fun(var) and lambda(var) while set(var = exp) sets the value of a variable to the result of the expression.

Global variables are defined by set(var = exp) when var is not a local variable at that time.

The runtime scope of a global variable is the runtime scope of the outermost query within which the variable is defined, including functions and subqueries (and sttl templates).

When a global variable is defined in a function, the global variable definition remains valid outside the function when the function resumes, until the outermost query resumes.

A local variable definition temporarily hides a global variable with the same name within the lexical scope of the statement that defines the local variable.

A global variable cannot be referenced directly in a SPARQL query,
however it can be accessed by means of a function call that returns the value of the global variable. In other words, global variables belong to LDScript, not to SPARQL.

This statement defines a loop on LDScript terms that are iterable datatypes. The list below specifies the kind of the term iterated in the for statement: for (VAR in EXP).

- VAR : Term in EXP : dt:list
- VAR : Term in EXP : dt:triple
- VAR : dt:triple in EXP : dt:path
- VAR : dt:triple in EXP : dt:graph
- VAR : dt:mapping in EXP : dt:mappings
- VAR : dt:list(xsd:string, Term) in EXP : dt:mapping where first element is the variable name and second is the variable value

The result returned by the for statement is the boolean value true. A specific result can be returned using the return statement.

for (?n in xt:list(1, 2, 3)) { if (us:prime(?n)) { xt:display(?n) } }

The for statement can take as argument a select-where query. In this case, the loop iterates on the solutions of the query and the variables projected in the select clause are bound to their value of the current solution in the body of the loop.

for (select ?x ?y where {?x foaf:knows ?y}) { us:foo(?x, ?y) }

It is possible to specify explicitly the variable(s) to be bound in the loop.

for (?m in select * where {?x foaf:knows ?y}) { let ((?x, ?y) = ?m) { ... } }

The for statement can take as second argument a construct-where query. In this case, the loop iterates on the triples of the result graph.

for (?t in construct where {?x foaf:knows ?y}) { let ((?s, ?p, ?o) = ?t) { ... } }

The access to the content of extension datatypes can be done by declarative pattern matching.

Iterable datatypes can be mapped to a list of variables, by pattern matching, using the let statement.

let ((?e1 ?e2 ?e3) = ?list)

let ((?t1 ?t2 ?t3) = ?graph)

let ((?s ?p ?o) = ?triple)

let ((?m1 ?m2 ?m3) = ?mappings)

Pattern matching with dt:mapping datatype is done by variable name and not by position. In the example below, variable ?x is bound to the value of variable ?x in current mapping.

let ((?x ?y) = ?mapping)

Extended datatypes can be accessed with pattern matching that focuses on first element(s), rest of the elements and last element(s). For this purpose, LDScript introduces two Pattern Matching operators: "." and "|" that can be combined.

The "." operator enables to identify last element(s) of an extension datatype. In the example below, ?z variable matches the last element whereas ?x variable matches the first element. If there is only one element, the first and the last element are the same. If the extended datatype is empty, the variables remain unbound but the statement does not fail.

let ((?x . ?z) = ?term)

It is possible to match several elements among the first ones and/or several elements among the last ones, as shown below. If there are not enough elements, some variables remain unbound.

let ((?x ?y . ?z ?t) = ?term)

The "|" operator enables LDScript to match a sublist of elements, after the first element(s). In the example below, the ?rest variable is bound with the sublist starting after the two first elements. The sublist may be empty if there are not enough elements.

let ((?x ?y | ?rest) = ?term)

It is possible to combine the two operators. In the example below, the sublist starts after the first two elements and stops before the last two elements. If there are not enough elements, the sublist may be empty.

let ((?x ?y | ?rest . ?z ?t) = ?term)

Sublist and last operators can be used on their own.

let (( | ?list) = ?term)

let (( | ?list . ?z ?t) = ?term)

let (( . ?z ?t) = ?term)

The "." and "|" operators can be used with these datatypes: dt:list, dt:map, dt:graph, dt:triple, dt:path, dt:mappings, dt:expression.

let ((?x ?y | ?rest . ?z ?t) = xt:iota(5)) ?x = 1 ; ?y = 2 ; ?rest = (3) ; ?z = 4 ; ?t = 5

let ((?x ?y | ?rest . ?z ?t) = xt:iota(4)) ?x = 1 ; ?y = 2 ; ?rest = () ; ?z = 3 ; ?t = 4

let ((?x ?y | ?rest . ?z ?t) = xt:iota(3)) ?x = 1 ; ?y = 2 ; ?rest = () ; ?z = 2 ; ?t = 3

let ((?x ?y | ?rest . ?z ?t) = xt:iota(2)) ?x = 1 ; ?y = 2 ; ?rest = () ; ?z = 1 ; ?t = 2

let ((?x ?y | ?rest . ?z ?t) = xt:iota(1)) ?x = 1 ; ?y is UNBOUND ; ?rest = () ; ?z is UNBOUND ; ?t = 1

LDScript extension datatypes can be iterated and mapped to a list of variables using the for statement.

for (?elem in ?list)

for (?triple in ?graph)

for ((?s ?p ?o) in ?graph)

for (?term in ?triple)

for (?mapping in ?mappings)

for ((?var ?val) in ?mapping)

A mappings datatype is iterated as mapping elements. Pattern matching with mapping element is done by variable name and not by position. In the example below, variable ?x and ?y are bound to the values of variable ?x and ?y in current mapping.

for ((?x ?y) in ?mappings)

This statement is a syntactic extension of SPARQL if then else statement.

if (?x > 0) { us:foo(?x) } else if (?y > 0) { us:bar(?y) } else { us:gee(?x, ?y) }

This statement resumes the execution of a function and returns its result.

term return(term t)

function us:test(?a, ?b) for (?x in xt:iota(?a, ?b)) { if (us:prime(?x)) { return(?x) } } }

This statement returns an error. The execution of the LDScript expression resumes and returns an error. It can be trapped by the coalesce statement as in SPARQL.

if (?x < 0) { error() }

A second order function is a function whose first argument evaluation returns a function (a function URI or a lambda expression) and which calls this function with the other arguments.
Second order functions are funcall, apply, map and reduce. The are useful in the context of Linked Data because the name of a function to be applied on resources can be determined by a SPARQL query.

We use the abstract function type to denote either the URI of a function or a lambda expression.

This statement applies a function which is the result of the evaluation of an expression. The first argument of the statement is an expression that must return either the URI of a function or a lambda expression.

term funcall (function fun, term t1, ... term tn)

funcall (us:getMethod(us:surface, ?x), ?x)

This statement applies a function to a list of arguments. The first argument of the statement is an expression that must return the URI of a function or a lambda expression.

term apply (function fun, dt:list arglist)

apply (rq:regex, xt:list("test", "e", "i"))

The map statement applies a function iteratively on the elements of an iterable datatype: dt:list, dt:graph, dt:mappings, dt:mapping. We use the abstract iterable type to denote any of these types. The first argument of the statement is an expression that must return the URI of a function or a lambda expression. SPARQL filter functions, as well as second order functions, are available as URI with the rq: prefix.

map (function fun, iterable term)

map (xt:display, xt:list(1, 2, 3))

The map functions described here can operate on two (or several) iterable datatypes. In this case, the function to be applied must have the same number of arguments as the number of iterable, and each iterable datatype is iterated at the same pace with the same index.

map (us:test, xt:list(1, 2, 3), xt:list(4, 5, 6))

The map functions described here can also have arguments that are not iterable datatypes. In this case, the same value of the argument is considered at each step of the iteration of the iterable datatypes.

map (us:fun, xt:list(1, 2, 3), 4)

The map functions described here can operate on iterable datatypes such as graph (iterate triple) or mappings (iterate mapping).

map (us:foo, query(select * where { ?x ?p ?y }))

The maplist statement applies a function on the elements of a list and returns the list of results

dt:list maplist (function fun, iterable term)

maplist (lambda(?x) { 1 / (?x * ?x) }, xt:list(1, 2, 3))

The mapfind statement search elements for which the function returns true. Function mapfind returns first of such elements or error() if there is no such element. In this latter case, error() can be trapped using coalesce().

term mapfind (function fun, iterable term)

mapfind (us:prime, xt:list(1, 2, 3))

The mapfindlist statement finds the elements of an iterable datatype for which the function returns true, return the list of such elements.

dt:list mapfindlist (function fun, iterable term)

mapfindlist (us:prime, xt:list(1, 2, 3))

The mapevery statement returns true if the function returns true on all elements, false otherwise.

xsd:boolean mapevery (function fun, iterable term)

mapevery (us:prime, xt:list(1, 2, 3))

The mapany statement returns true if the function returns true on any element, false otherwise.

xsd:boolean mapany (function fun, iterable term)

mapany (lambda(?y) { exists { ?x ?p ?y } }, xt:list(1, 2, 3))

This statement applies a binary function iteratively to a list of arguments and produces one final result. The first argument of the statement is an expression that must return the URI of a function or a lambda expression.

term reduce (function fun, dt:list list)

reduce (rq:plus, xt:iota(5)) = 15

Second order functions are available with the rq: prefix and can be combined.

reduce(rq:concat, maplist(rq:funcall, xt:list(rq:year, rq:month, rq:day, rq:hours, rq:minutes, rq:seconds), now()))

LDScript introduces general purpose extension functions.

Display RDF terms in Turtle syntax.

xt:display(term t...)

Display RDF terms string value.

xt:print(term t...)

Return a xsd:string Turtle representation of an RDF term.

xsd:string xt:turtle(RDF term)

Return a xsd:string representation of the content of an extension datatype in the dt: namespace.

xsd:string xt:content(LDScript term ?t)

Return the result of the evaluation of its argument.

term xt:self(term t)

Return the current focus graph.

dt:graph xt:graph()

The xt:load function implements URI dereferencing, it returns the graph resulting from the parsing of an RDF document.

dt:graph xt:load(URI uri)

If there is a graph argument, the RDF document is loaded in the graph.

dt:graph xt:load(URI uri, dt:graph g)

The first argument MUST returns a graph with datatype dt:graph. The focus statement evaluates the second argument with the graph as current dataset.

term xt:focus(dt:graph g, exp e)

xt:focus( xt:load(<http://example.org/test>), exists { ?x rdf:value 2.718 })

LDScript inherits STTL transformation language API that enables users to apply transformations to RDF entities such as Turtle, RDF/XML or JSON transformations to RDF graphs and resources or the functional syntax transformation of OWL ontologies. Note that these functions belong to the st: namespace. In the example below, st-uri is the name of a transformation: st:turtle, st:rdfxml, st:json, st:owl.

xsd:string st:apply-templates-with(st-uri) xsd:string st:apply-templates-with(st-uri, term) xsd:string st:apply-templates-all(term) xsd:string st:apply-templates-with-all(st-uri, term) xsd:string st:apply-templates-graph(graph-uri) xsd:string st:apply-templates-with-graph(st-uri, graph-uri) xsd:string st:call-template(uri, term_1, .., term_n) xsd:string st:call-template-with(st-uri, uri, term_1, .., term_n)

LDScript provides functions to evaluate SHACL shapes on the current focus graph. The result of shape functions is the validation report graph.

Test a whole shape graph or a specific shape on the focus graph.

dt:graph xt:shapeGraph(URI shapeGraph) dt:graph xt:shapeGraph(URI shapeGraph, URI shape)

Test a whole shape graph or a specific shape on a specific node.

dt:graph xt:shapeNode(URI shapeGraph, URI nodeURI) dt:graph xt:shapeNode(URI shapeGraph, URI shape, URI nodeURI)

Test whether the validation report is conform.

xsd:boolean xt:conform(dt:graph g)

Generate the SPIN RDF graph of a SPARQL string query.

dt:graph xt:spin(xsd:string q)

Generate text format for SPARQL select query results.

xsd:string xt:xml (dt:mappings m) xsd:string xt:rdf (dt:mappings m) xsd:string xt:json(dt:mappings m)

The objects of the language are RDF terms and LDScript terms. RDF terms are, as usual, URI, Blank Node and Literal with XSD datatype. LDScript terms are RDF graph and triple, SPARQL query solution sequence (called mappings), SPARQL query solution (called mapping) and SPARQL property path solution (called path). LDScript terms also include list whose elements are LDScript objects and map whose keys and values are LDScript objects.

LDScript objects other than RDF terms are implemented by means of literals with specific extension datatypes in the dt: namespace: dt:list, dt:map, dt:graph, dt:triple, dt:path, dt:mappings, dt:mapping. Hence, they are also implemented as RDF terms (i.e. RDF literals with extension datatypes) and their content can be accessed by specific statements as shown below. These datatypes are iterable by means of the for and map statements. By extension, we call LDScript terms the objects of the language.

The dt:list extension datatype implements list of LDScript terms, including lists. Although similar, it is distinct from RDF list (rdf:List class with rdf:first, rdf:rest and rdf:nil). List elements need to be neither of the same kind nor of the same datatype. The dt:list datatype is provided with a set of functions.

The xt:list function is the list constructor.

dt:list xt:list(term t...)

xt:list(1, 2, 3) xt:list(xt:list(1, 2), xt:list(3, 4))

The xt:iota function generates a list of successive integers or characters.

dt:list xt:iota(term t) dt:list xt:iota(term t1 , term t2)

xt:iota(5) = xt:list(1, 2, 3, 4, 5) xt:iota(5, 7) = xt:list(5, 6, 7) xt:iota('a', 'c') = xt:list('a', 'b', 'c')

The xt:size function returns the number of elements of a list.

xsd:integer xt:size(dt:list list)

The xt:first function returns the first element of a list.

term xt:first(dt:list list)

The xt:rest function returns the sublist after the first element..

dt:list xt:rest(dt:list list)

The xt:get function returns the nth element of a list.

term xt:get(dt:list list, xsd:integer n)

The xt:set function sets the value of the nth element of a list. Error if there is no nth element.

xt:set(dt:list list, xsd:integer n, term t)

The xt:add function adds a tail element to a list. List is modified.

xt:add(dt:list list, term t)

The xt:add function inserts/adds element to a list at nth place. List is modified.

xt:add(dt:list list, xsd:integer n, term t)

The xt:cons function adds a head element to a list. Returns copy of list.

dt:list xt:cons(term t, dt:list list)

The xt:remove function removes the first occurrence of an element from the list, if it is present. Modify the list.

xt:remove(dt:list list, term t)

The xt:removeindex function removes the nth element of the list. Modify the list.

xt:removeindex(dt:list list, xsd:integer n)

The xt:append function appends two lists, keep duplicates.

dt:list xt:append(dt:list l1, dt:list l2)

The xt:merge function merges two lists and removes duplicates.

dt:list xt:merge(dt:list l1, dt:list l2)

The xt:reverse function reverses a list.

dt:list xt:reverse(dt:list list)

The xt:sort function sorts a list.

dt:list xt:sort(dt:list list)

Translate recursively an rdf:List into a dt:list.

select ?x (us:list(?l) as list) where { ?x rdf:value ?l . } function us:list(?l) { let ( select ?l (aggregate(if (?b, us:list(?e), if (?e = rdf:nil, xt:list(), ?e))) as ?list) where { ?l rdf:rest*/rdf:first ?e bind (exists { ?e rdf:rest ?a } as ?b) }) { ?list } }

The dt:map extension datatype implements a Map whose keys and values are LDScript objects.

It is provided with a xt:map constructor function.

dt:map xt:map()

The xt:size function returns the size of the map.

The xt:set function enables users to set a key value pair in the map.

xt:set(dt:map map, term key, term value)

The xt:get function enables users to retrieve the value of a key in the map.

term xt:get(dt:map map, term key)

The dt:map datatype is iterable as pairs (key value).

for ((?key ?value) in ?map)

There are two datatypes for RDF entities, dt:graph for RDF graph and dt:triple for RDF triple. They are iterable as shown below.

let (?g = construct where { ?x ?p ?y }) { for (?t in ?g) { for (?x in ?t) { } } } datatype(?g) = dt:graph datatype(?t) = dt:triple ?x : RDF term

The dt:graph datatype is provided with functions. Function xt:size returns the number of triples of a graph.

xsd:integer xt:size(dt:graph g)

Function xt:union computes a graph that is the union of two graphs. The arguments are LDScript terms with dt:graph datatype and the result is returned as a LDScript term with dt:graph datatype.

dt:graph xt:union(dt:graph g1, dt:graph g2)

The dt:triple datatype is provided with functions to access the subject, the property and the object. Implementations MAY provide a function to access the named graph when triples are quads.

term xt:subject(dt:triple t) term xt:property(dt:triple t) term xt:object(dt:triple t) term xt:graph(dt:triple t)

There are three datatypes for SPARQL entities: dt:mappings for SPARQL Query solution sequence, dt:mapping for SPARQL Query solution and dt:path for Property Path solutions. They are iterable as shown below.

let (?s = select * where { ?x ?p ?y }) { for (?m in ?s) { for ((?var, ?val) in ?m) { } } } datatype(?s) = dt:mappings datatype(?m) = dt:mapping datatype(?var) = xsd:string ?val : RDF term

The dt:mappings datatype is provided with a function xt:size that returns the number of solutions.

xsd:integer xt:size(dt:mappings m)

The dt:mappings datatype is provided with functions that perform SPARQL algebra operations on SPARQL query solutions of select-where queries. The results are returned as literals with dt:mappings datatype.

dt:mappings xt:join(dt:mappings m1, dt:mappings m2) dt:mappings xt:union(dt:mappings m1, dt:mappings m2) dt:mappings xt:minus(dt:mappings m1, dt:mappings m2) dt:mappings xt:optional(dt:mappings m1, dt:mappings m2)

The dt:path datatype is provided for the case where the implementation gives access to Property Path solutions. It is provided with the xt:size function.

The syntax is given in EBNF and relies on SPARQL syntax.

LDScript ::= SPARQL_QueryUnit Fun Fun ::= (Annotation Function | Annotation Package)* Function ::= 'function' Uri FunVarList Body Package ::= 'package' '{' Function+ '}' Annotation ::= ( '@public' | '@debug' )* Body ::= '{' Exp? (';' Exp)* '}' Exp ::= SPARQL_Constraint -- with BuiltInCall extended below BuiltInCall ::= SPARQL_BuiltInCall | Let | For | If | Return | Error | SecondOrder | Lambda | Query SecondOrder ::= Funcall | Apply | MapFun | Reduce | Query ::= 'query' '(' (SelectQuery | ConstructQuery) (',' Exp)? ')' ExpQuery = Exp | '@' List | SelectQuery | ConstructQuery Let ::= 'let' '(' LetDecl (',' LetDecl)* ')' Body | 'let' '(' SelectQuery ')' Body LetDecl ::= Var '=' ExpQuery | VarExp '=' ExpQuery VarExp ::= '(' VAR+ ('|' VAR )? ('.' VAR+)? ')' VarList ::= '(' Var (Var)* ')' FunVarList ::= '(' Var? (',' Var)* ')' For ::= 'for' '(' Var 'in' ExpQuery ')' Body | 'for' '(' VarList 'in' ExpQuery ')' Body | 'for' '(' SelectQuery ')' Body If ::= 'if' '(' Exp ')' Body ('else' ( Body | If )) ? Funcall::= 'funcall' '(' Exp (',' Exp)* ')' Apply ::= 'apply' '(' Exp ',' Exp ')' Reduce ::= 'reduce' '(' Exp ',' Exp ')' MapFun ::= Map '(' Exp (',' Exp)+ ')' Map ::= 'map' | 'maplist' | 'mapfind' | 'mapfindlist' | 'mapany' | 'mapevery' Lambda ::= 'lambda' LambdaVarList Body LambdaVarList ::= FunVarList | '(' VarList ')' Error ::= 'error' '(' ')' Return ::= 'return' '(' Exp ')' List ::= '(' (RDFTerm | List)* ')'

LDScript enables us to propose and implement natural SPARQL extensions.

LDScript statements MAY be available within extended SPARQL Query Filter Constraints.

select * where { ?x us:method [ us:name us:validate ; us:function fun ] filter funcall(?fun, ?x) }

This statement defines an extension aggregate. The first expression (e.g. aggregate(?n)) is the expression to aggregate. The aggregate function computes the list of values of this expression. The second expression (e.g. us:median(?list)) is the function to be applied to the list of values. In the example below, the aggregate computes the median of the values.

select (aggregate(?n) as ?list) (us:median(?list) as ?med) where { ?x rdf:value ?n } function us:median(?list) { xt:get(xt:sort(?list), xsd:integer(xt:size(?list) / 2)) }

The aggregate function can also be used alone, in this case the list of values is returned.

select (aggregate(distinct ?n) as ?list) where { ?x rdf:value ?n }

This statement unnests a list of values in a SPARQL values statement. It is equivalent to a values clause where the values would be computed.

values ?n { (unnest(xt:list(1, 2, 3)) }

It is equivalent to the values clause below.

values ?n { 1 2 3 }

Values unnest can be used on: list, mappings, graph.

The dt:path datatype is provided for the case where the implementation gives access to Property Path solutions. In the example below, SPARQL is extended with path variables, the $path variable is bound to the property path that relates ?x and ?y. The datatype of the value of $path is dt:path. It is conceptually equivalent to dt:list(dt:triple).

select * where { ?x foaf:knows+ :: $path ?y }

When a variable has for value an RDF graph, the variable can be used in a named graph pattern and the named graph pattern is evaluated on the content of the graph. The example below shows this case with variable ?g.

let (?g = construct { .. } where { .. }) { query(select * where { graph ?g { .. }}) }

Aggregate ::= SPARQL_Aggregate | 'aggregate' '(' ('distinct')? Exp ')' ValuesClause ::= SPARQL_ValuesClause | 'values' Var '{' 'unnest' '(' Exp ')' '}' | 'values' VarList '{' 'unnest' '(' Exp ')' '}' VerbPath ::= Path ( '::' Var )?

In an ontology, properties may be defined as functions of other properties. For example, the surface can be defined as the product of the length and the width.

select * where { ?x a us:Figure bind (us:surface(?x) as ?s) } function us:surface(?x) { let ((?w, ?l) = select * where { ?x us:width ?w ; us:length ?l }) { ?w * ?l } }

Implement a function as a service.

function us:service(?x) { let (select ?x ?l where { service <http://fr.dbpedia.org/sparql> { ?x rdfs:label ?l}}) { ?l } }

Functions can be used to program approximate match.

select * where { ?x a ?t filter us:match(foaf:Person, ?t) } function us:match(?q, ?t) { exists { { ?t rdfs:subClassOf* ?q } union { ?q rdfs:subClassOf/(rdfs:subClassOf*|^rdfs:subClassOf) ?t } } }

Functions can be used to program recursive match.

select * where { ?x a foaf:Person ?y a foaf:Person filter us:match(?x, ?y) } function us:match(?x, ?y) { exists { { ?x foaf:knows ?y } union { ?x foaf:knows ?z . ?y a foaf:Person filter us:match(?z, ?y) } } }

A SPARQL interpreter may define a set of events and emit events during quering processing. A SPARQL interpreter may be provided with an event manager that traps events. If a SPARQL query is provided with appropriate function definitions for the events, the event manager calls these functions. The association between an event and a function is done by an annotation wich is an identifier prefixed by the '@' character. The function name is free whereas the annotation name is fixed.

Function called when query processing starts.

@before function us:before(?query)

Function called when query processing resumes.

@after function us:after(?mappings)

Function called when a solution is found.

@result us:result(?mapping)

LDScript can be use to manage predefined queries by means of lambda expressions.

function us:foo() { let (?list = xt:list( lambda() { query(select .. where ..) }, lambda() { query(select .. where ..) } )) { maplist(rq:funcall, ?list) } }

LDScript is implemented and available in the Corese Semantic Web Factory.

Dedicated programming language enabling Semantic Web programmers to define functions on RDF terms, triples and graphs or SPARQL query results can facilitate the development and improve the reuse and maintenance of the code produced for Linked Data. We propose to extend SPARQL with LDScript, a script language that enables users to define extension functions. Its main characteristics are:

- Design on top of SPARQL Filter language
- Function definition
- Lambda expression
- SPARQL predefined functions, including exists clause
- LDScript predefined functions
- Select and construct SPARQL query
- Second order functions: funcall, apply, map, reduce
- Statements: let, for, if then else, return
- Pattern matching
- Object: RDF term, XSD datatype and extension datatype

In the future we wish to provide a second implementation on top of another Semantic Web Factory. We wish to provide a compiler to Java language and work on performance. We would like to design a type checker and investigate Linked Functions.

Olivier Corby, Catherine Faron-Zucker and Fabien Gandon, LDScript: a Linked Data Script Language, International Semantic Web Conference, ISWC, 2017 October, Vienna, Austria.

RDF 1.1 Concepts and Abstract Syntax, Graham Klyne, Jeremy J. Carroll, Brian McBride. W3C Recommendation, February 2014

SPARQL 1.1 Query Language, Steve Harris, Andy Seaborne. W3C Recommendation, March 2013