Semantic Web Tools

Prolog’s logic-based foundation makes it a natural fit for working with Semantic Web technologies — RDF, RDFS, OWL, and SPARQL. SWI-Prolog provides mature libraries for all of these.

Loading and Querying RDF Data

The Resource Description Framework (RDF) represents knowledge as a directed graph of subject-predicate-object triples. SWI-Prolog includes a highly optimized RDF database in its semweb library package.

The Turtle Format

While RDF can be serialized in XML (RDF/XML) or JSON (JSON-LD), the Turtle (Terse RDF Triple Language) format is the standard, human-readable serialization. In Turtle, triples are declared as space-separated terms ending with a period:

1 @prefix ex: <http://example.org/> .
2 ex:swi_prolog ex:implements ex:prolog .

You can group multiple statements about the same subject using a semicolon (;) or comma (,):

1 ex:prolog a ex:Language ;
2     rdfs:label "Prolog" ;
3     ex:paradigm ex:LogicProgramming .

Loading and Querying in Prolog

The library(semweb/rdf_db) module stores all loaded RDF triples in an internal database, which we query using rdf(?Subject, ?Predicate, ?Object). To parse Turtle files, we import library(semweb/turtle).

Architecture diagram for the RDF Explorer example
Figure 17. Architecture diagram for the RDF Explorer example

The rdf_explorer project wraps SWI-Prolog’s semweb library. Here is the complete file rdf_explorer/prolog/rdf_loader.pl:

 1 %% rdf_loader.pl - Load and query RDF data using SWI-Prolog's semweb
 2 %% library
 3 :- module(rdf_loader, [
 4     load_rdf_file/1,
 5     query_rdf/3,
 6     list_subjects/0,
 7     describe_resource/1
 8 ]).
 9 
10 :- use_module(library(semweb/rdf_db)).
11 :- use_module(library(semweb/rdfs)).
12 :- use_module(library(semweb/turtle)).
13 
14 %% load_rdf_file(+FilePath) - Load RDF from Turtle or RDF/XML file
15 load_rdf_file(FilePath) :-
16     rdf_load(FilePath).
17 
18 %% query_rdf(?S, ?P, ?O) - Query the RDF triplestore
19 query_rdf(S, P, O) :- rdf(S, P, O).
20 
21 %% list_subjects - Print all unique subjects
22 list_subjects :-
23     setof(S, P^O^rdf(S, P, O), Subjects),
24     forall(member(S, Subjects), format("  ~w~n", [S])).
25 
26 %% describe_resource(+URI) - Print all triples for a given subject
27 describe_resource(URI) :-
28     format("Describing: ~w~n", [URI]),
29     forall(
30         rdf(URI, P, O),
31         format("  ~w -> ~w~n", [P, O])
32     ).

Querying Remote SPARQL Endpoints

To query RDF data across the web, we use SPARQL (SPARQL Protocol and RDF Query Language). SWI-Prolog provides a standard SPARQL client in library(semweb/sparql_client). This module sends a SPARQL SELECT query over HTTP to a remote endpoint and parses the returned JSON or XML results into Prolog terms.

A SPARQL query typically specifies variables starting with a question mark (e.g. ?developer). The result of a query is returned as individual row(Value1, Value2, ...) terms containing the unified results for each row.

Architecture diagram for the SPARQL Client example
Figure 18. Architecture diagram for the SPARQL Client example

The sparql_client project provides convenient wrappers for sending queries. Here is the complete file sparql_client/prolog/sparql.pl:

 1 %% sparql.pl - SPARQL client for querying remote endpoints
 2 :- module(sparql, [
 3     sparql_query_dbpedia/2,
 4     sparql_query_wikidata/2,
 5     sparql_query/3
 6 ]).
 7 
 8 :- use_module(library(semweb/sparql_client)).
 9 :- use_module(library(http/http_client)).
10 
11 %% sparql_query_dbpedia(+Query, -Results)
12 sparql_query_dbpedia(Query, Results) :-
13     sparql_query(Query, Results,
14                  [host('dbpedia.org'), path('/sparql')]).
15 
16 %% sparql_query_wikidata(+Query, -Results)
17 sparql_query_wikidata(Query, Results) :-
18     sparql_query(Query, Results,
19                  [host('query.wikidata.org'), path('/sparql')]).

RDFS and OWL Reasoning

Standard RDF only represents direct relationships. To perform semantic reasoning, we use RDF Schema (RDFS) and the Web Ontology Language (OWL). RDFS introduces properties like:

  • rdfs:subClassOf: Declares hierarchical class inheritance.
  • rdfs:subPropertyOf: Declares subproperty inheritance.
  • rdfs:domain / rdfs:range: Restricts the types of subjects and objects a property can link.

Prolog is well-suited to reason over these rules, but SWI-Prolog’s library(semweb/rdfs) implements them directly in highly optimized C-level hooks, saving you from writing recursive rules yourself.

The most important predicates are:

  • rdfs_individual_of(?Resource, ?Class): Succeeds if Resource is an instance of Class, resolving any transitive class inheritance (via subClassOf) and property domains/ranges.
  • rdfs_subclass_of(?SubClass, ?SuperClass): True if SubClass is a subclass of SuperClass (either directly or transitively).
  • rdfs_subproperty_of(?SubProperty, ?SuperProperty): True if SubProperty inherits from SuperProperty.

For example, if we load an ontology stating that ex:LogicProgramming is a subclass of ex:ProgrammingParadigm, and ex:prolog has paradigm ex:LogicProgramming, the standard rdf/3 query won’t show that Prolog is a ProgrammingParadigm. However, RDFS reasoning unifies it immediately:

1 ?- rdfs_individual_of('http://example.org/prolog', 'http://example.org/ProgrammingParadigm').
2 true.

Practical Applications

You can combine local RDF data with remote SPARQL queries to build a domain-specific knowledge explorer. This hybrid architecture allows you to maintain private, local triples (such as proprietary client records or local system settings) while enriching them dynamically with global, public information from Wikidata or DBpedia.

Here is a Prolog module showing this pattern:

 1 :- module(knowledge_explorer, [
 2     explore_and_enrich/1
 3 ]).
 4 
 5 :- use_module(library(semweb/rdf_db)).
 6 :- use_module(sparql_client/prolog/sparql).
 7 
 8 %% explore_and_enrich(+ResourceURI)
 9 %% 1. Find and print all local facts about the resource.
10 %% 2. Query DBpedia to fetch the English abstract/description.
11 explore_and_enrich(ResourceURI) :-
12     format("=== Local Knowledge for ~w ===~n", [ResourceURI]),
13     forall(
14         rdf(ResourceURI, P, O),
15         format("  Local: ~w -> ~w~n", [P, O])
16     ),
17     
18     % Extract the local name from the URI to query DBpedia
19     % e.g., 'http://example.org/Prolog' -> "Prolog"
20     file_base_name(ResourceURI, LocalName),
21     format("~n=== Querying DBpedia for ~w ===~n", [LocalName]),
22     
23     format(string(Query),
24         "SELECT ?abstract WHERE { \n\
25          <http://dbpedia.org/resource/~w> <http://dbpedia.org/ontology/abstract> ?abstract . \n\
26          FILTER (lang(?abstract) = 'en') \n\
27          } LIMIT 1", [LocalName]),
28          
29     (   catch(sparql_query_dbpedia(Query, row(literal(Abstract))), _, fail)
30     ->  format("  Abstract: ~w~n", [Abstract])
31     ;   writeln("  Could not fetch remote abstract.")
32     ).

This pattern keeps your local codebase small and lightweight while placing the billions of triples of the Semantic Web at your logic engine’s disposal.