Fountain depicting the Fox and the Stork. Image credit: Jordiferrer

Aesopica, Part 5: Blank Nodes

This article is the fifth part of a series, examining the use of the Clojure language for representing Linked Data, using examples from Aesop’s stories. The topic of this article is to explain the somewhat contentious subject of blank nodes.

One of the strengths of RDF as a graph representation format is the way resources are named. Through the use of Uniform Resource Identifiers (URIs) every element of the graph can be uniquely identified. With such generic and powerful facilities for naming it can easily represent information in any domain. For example on schema.org elements have been defined for the notion of a medical condition (http://schema.org/MedicalCondition), employee (http://schema.org/employee) and bank account (http://schema.org/BankAccount), just to name a few. In particular the namespacing part of the URI, e.g.: http://schema.org/ for http://schema.org/employee , helps to ensure that concepts can be uniquely named, even in scenarios with multiple definitions of the same concept.

Blank nodes go against this notion of making everything explicitly named. In fact an alternative name for blank nodes are anonymous resources. Instead of giving a resource an explicit name with a URI a placeholder is used. This indicates the existence of the resource, but does not tie it together with a namespaced name. In the Turtle syntax for RDF we can use a label prefixed by _: to indicate a blank node. For example, the following RDF graph states that the fox and the stork both give out an invitation.

@base <http://www.newresalhaider.com/ontologies/aesop/foxstork/> .

<#fox> <#gives-invitation> _:invitation1.
<#stork> <#gives-invitation> _:invitation2.

Due to the invitations having different names we can expect them to be different resources, However the exact names of these resources do not matter. For example the RDF triples below have arguably the same meaning:

@base <http://www.newresalhaider.com/ontologies/aesop/foxstork/> .

<#fox> <#gives-invitation> _:abc.
<#stork> <#gives-invitation> _:xyz.

It is important to reiterate that the blank nodes are not resource identifiers such as URIs. They are also only local in scope: an _:invitation1 in one graph and a _:invitation1 in another are not referring to the same thing. Even resources used in the similar places in different graphs are not the same. For example the _:abc and the _:invitation1 used in the above graphs, while expressing the same meaning, are not the same resource.

The above features are both the strength and the weakness of using anonymous resources. We are not required to use a specific named identifier, but this makes referring to resources and comparing them more difficult.

There are number of scenarios where such anonymous resources can be useful. For example, when representing complex structures not easily expressed in triples where we would need “placeholder nodes” in the graph but do not particularly care about its naming. In other cases we want to hide some information, and blank nodes could be used as placeholders for named resources. A good overview of the various uses of Blank Nodes can be found in the paper: On Blank Nodes.

With the above description of blank nodes, we also want to provide something similar in our Clojure based RDF representation as well. As we are using keywords to represent URIs in our Clojure representation (i.e. :fox and :rdf/type), a natural element to differentiate them is to use symbols for blank nodes. Symbols are created in Clojure by prefixing it with '. For example in the Clojure based RDF representation we use blank nodes for representing invitations:

(def fox-and-stork-blank-node-edn
  {::aes/context
   {nil "http://www.newresalhaider.com/ontologies/aesop/foxstork/"
    :rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}
   ::aes/facts
   #{[:fox :rdf/type :animal]
     [:stork :rdf/type :animal]
     [:fox :gives-invitation 'invitation1]
     ['invitation1 :has-invited :stork]
     ['invitation1 :has-food :soup]
     ['invitation1 :serves-using :shallow-plate]
     [:stork :gives-invitation 'invitation2]
     ['invitation2 :has-invited :fox]
     ['invitation2 :has-food :crumbled-food]
     ['invitation2 :serves-using :narrow-mouthed-jug]
     [:fox :can-eat-food-served-using :shallow-plate]
     [:fox :can-not-eat-food-served-using :narrow-mouthed-jug]
     [:stork :can-eat-food-served-using :narrow-mouthed-jug]
     [:stork :can-not-eat-food-served-using :shallow-plate]}})

The above is our version of the Fox and the Stork story that we previously explored in this series of articles The main difference is that two blank nodes are used: 'invitation1 and 'invitation2, instead of named resources. Perhaps the story teller might want to hide some details of their invitations.

As with all the previous features in this series of articles, blank nodes been implemented in our implementation of this syntax in the Aesopica library for using Clojure to write Linked Data. Hiding details with blank nodes, even in Clojure, is now just one library away.

Avatar
Newres Al Haider
Postdoctoral Researcher and Software Engineer

Related