With my initial work on developing a Clojure environment for OWL (n.d.a) I was focused on producing something similar to Manchester syntax (n.d.b/) Here, I describe my latest extensions which makes more extensive use of Lisp atoms. The practical upshot of this should be to reduce errors due to spelling mistakes, as well as enabling me to add simple checks for correctness.
The desire for a simple syntax is an important one. I would like my library to be usable by people not experienced with Lisp, although I am clearly aware that this sort of environment is likely to be aimed at those with some programming skills. I have managed to produce a syntax which, I think, is reasonable straight forward. It has more parentheses than Manchester syntax, but is easier in other ways, especially now that I have learnt a little more about how Clojure namespaces work. For example, this defines a class in OWL.
(owlclass "HumanArm" :subclass "Arm" (some "isPartOf" "Human") :annotation (comment "The Human arm is an Arm which is part of a human"))
One of my initial desires for the Clojure mode was to enable the use of
standard tools that we have come to expect from a modern programming
language, which should enable us to build a more pragmatic ontology
The first of these is a unit testing environment. Clojure already has
one of these integrated. So far, I have only used this for testing my
own code; so, for example, this is the current unit test for the
owlclass function used above.
(deftest owlclass (is (= 1 (do (o/owlclass "test") (.size (.getClassesInSignature (#'o/get-current-jontology)))))) (is (instance? org.semanticweb.owlapi.model.OWLClass (o/owlclass "test"))))
There are, however, some limitations to the approach that I have taken so far. Consider this statement:
(owlclass "HumanArm" :subclass (some "isPartOf" "Humn") "Arm" )
This is broken because I have referred to the class
Humn which I
probably do not want to exist because I have spelt it wrongly.
Unfortunately, as it stands my code does not know this and so will
create the class “Humn”. Now, this form of error is not that likely to
happen; tools such as Kudu
enforce this correctness in the Editor, while pabbrev.el
“correctness-by-completion”. None the less, these errors will happen and
I do not want them to. There are a variety of ways that I could build
this form of checking in — generally, this would involve introspecting
over the ontology to see if classes already exist.
However, I have taken a different approach, so that I can use the Lisp itself to prevent the problem. To do this, for each class created, I generate a new Lisp symbol; likewise, object property and the ontology itself. The practical upshot of this, I that I can write code like so:
(defclass a) (defclass b :subclass a) (defoproperty r) (defclass d :subclass (some r b)) ;; will fail as f does not exist (defclass e :subclass f) ;; will fail as r and b are the wrong way around (defclass e :subclass (some b r))
The advantages are three-fold. Firstly, it’s slightly shorter, and there is no need to use quotes all over the place. Secondly, it is no longer possible to refer to a class that has not yet been defined; Clojure will pick this up immediately; from the user perspective, you can test your statements as you go, as soon as you have written them, by evaluating them. Finally, because the atoms carry values which are typed, we can also detect errors such as using a property when a class is necessary.
Of course, the original functions are all still in place; there would be no point defining symbols if the intention was to use the API entirely programmatically. But, my intention for Clojure-OWL is to have environment for humans (well, programmers anyway) to develop ontologies with.
There is a final advantage to this, that I have not yet exploited.
Currently, I have generated the name of the OWL class directly from the
symbol name. So, in the above example the class
a will have a name
a”. There are some problems with this. Not all characters are legal
in Clojure symbol names nor in OWL class names, and the set of
characters is not the same. So, while this is a useful default, I will
formally separate these. At the same time, I think that this will allow
me to address a second problem, that of semantics vs semantics free
identifiers (n.d.f) I
can call a class, ontology or object property anything at all, and refer
to it with a easy to remember identifier. I might use something like
(defoproperty has_part :name "BFO_OOOOO51")
The is still a significant amount of work to do yet; I haven’t made a complete coverage of OWL yet, just the most important parts (i.e. the bits that I use most often). Next, I need to start building some predicates so I can test (asserted) subclass relationships. So far, however, this approach is showing significant promise.
———. n.d.f. https://www.russet.org.uk/blog/2040.
———. n.d.e. https://www.russet.org.uk/blog/2161.
———. n.d.b. https://www.w3.org/TR/owl2-manchester-syntax.