Yesterday, I finally took some time to package the little hack
I've written last week, based on the SWI N3 parser I wrote back in
September.
Update: A newer version with lots of bug fixes is available
there.
It's called Henry,
it is really small, hopefully quite understandable, and it does N3
reasoning. The good thing is that you can embed such reasoning in the SWI Semantic Web Server, and
then access a N3-entailed RDF store using SPARQL.
How to use it?
Just get this
tarball, extract it, and make sure you have SWI-Prolog installed, with its Semantic Web library.
Then, the small swicwm.pl script provides a small command-line
tool to test it (roughly equivalent, in CWM terms, to cwm $1
-think -data -rdf).
Here is a simple example (shipped in the package, along other funnier
examples).
The file uncle.n3 holds:
prefix : <http://example.org/> .
:yves :parent :fabienne.
:fabienne :brother :olivier.
{?c :parent ?f. ?f :brother ?u} => {?c :uncle ?u}.
And:
$ ./swicwm.pl examples/uncle.n3
<!DOCTYPE rdf:RDF [
<!ENTITY ns1 'http://example.org/'>
<!ENTITY rdf 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
]>
<rdf:RDF
xmlns:ns1="&ns1;"
xmlns:rdf="&rdf;"
>
<rdf:Description rdf:about="&ns1;fabienne">
<ns1:brother rdf:resource="&ns1;olivier"/>
</rdf:Description>
<rdf:Description rdf:about="&ns1;yves">
<ns1:parent rdf:resource="&ns1;fabienne"/>
<ns1:uncle rdf:resource="&ns1;olivier"/>
</rdf:Description>
</rdf:RDF>
How does it work?
Henry is built around my SWI N3 parser, which
basically translates the N3 in a quad form, that can be stored in the SWI RDF store. The two
tricks to reach such a representation are the following:
- Each formulae is seen as a named graph, identified by a blank node (there
exists a graph, where the following is true...);
- Universal quantification is captured through a specific set of atoms (a bit
like
__bnode1 captures an existentially quantified variable).
For example:
prefix : <http://example.org/> .
{?c :parent ?f. ?f :brother ?u} => {?c :uncle ?u}.
would be translated to:
subject||predicate||object||graph
__uqvar_c||http://example.org/parent||__uqvar_f||__graph1
__uqvar_f||http://example.org/brother||__uqvar_u||__graph1
__uqvar_c||http://example.org/uncle||__uqvar_u||__graph2
__graph1||log:implies||__graph2||uncle.n3
Then, when running the compile predicate, such a representation
is translated into a bunch of Prolog clauses, such as, in our example:
rdf(C,'http://example.org/uncle',U) :- rdf(C,'http://example.org/parent',F), rdf(F,'http://example.org/brother',U).
Such rules are defined in a particular entailment module, allowing it to be
plugged in the SWI Semantic Web server. Of course, rules can get into an
infinite loop, and this will make the whole thing crash :-)
I tried to make the handling of lists and builtins as clear and simple as
possible. Defining a builtin is as simple as registering a new predicate,
associating a particular URI to a prolog predicate (see
builtins.pl for an example).
An example using both lists and builtins is the following one. By issuing
this SPARQL query to a N3-entailed store:
PREFIX list: <http://www.w3.org/2000/10/swap/list#>
SELECT ?a
WHERE
{?a list:in ("a" "b" "c")}
You will get back a, b and c (you
have no clue how much I struggled to make this thing work :-) ).
But take care!
Of course, there are lots of bugs and issues, and I am
sure there are lots of cases where it'll crash and burn :-) Anyway, I hope it
will be useful.