Disjoints in Clojure-owl
When I started work on Clojure-owl the original intention was to provide myself with a more programmatic environment for writing ontologies, where I could work with a full programming language at to define the classes I wanted [@url:www.russet.org.uk/blog/2214] After some initial work with functions taking strings, I have moved to an approach where classes (and other ontological entities), are each assigned to a Lisp symbol [@url:www.russet.org.uk/blog/2254] I'm using "symbol" rather than "atom" because its a bit more accurate, especially as Clojure uses "atom" with a different meaning.
This means that I now have something which allows me to write ontological terms looking something like this:
(defclass a) (defclass b :subclass a) (defoproperty r) (defclass d :subclass (some r b))
While this is quite nice, and looks fairly close to Manchester syntax [@url:www.w3.org/TR/owl2-manchester-syntax/] ultimately, so far all this really provides me with is a slightly complex mechanism for achieving what I could already do; which raises the questions, why not just use Manchester syntax? Why bother with the Lisp if this is all I am to achieve?
I think I have now got to the point where the advantages are starting to show through, as I have started to create useful macros, which operate at a slightly higher level of abstraction from Manchester syntax. I will explain this using examples, perhaps inevitably, based around pizza [@url:robertdavidstevens.wordpress.com/2010/01/22/why-the-pizza-ontology-tutorial/] which I have started to develop using Clojure-owl.
First I wanted to be able to define several classes at once, rather than
having to use a somewhat long-winded
defclass form for each; for this
I have written a macro called
declare-classes --- perhaps a slight
misnomer, as it also adds the classes to the ontology. This example
shows the purpose:
(declare-classes GoatsCheeseTopping GorgonzolaTopping MozzarellaTopping ParmesanTopping)
In practice, this may not be that useful for an ontology builder, as it creates a bare class; no documentation, nothing else. It may be useful for forward-declaration (like Clojure declare).
One slightly unfortunate consequence of the decision to use lisp symbols
is I know find myself writing a lot of macros. For those who have not
used lisp before, most work is done with functions. Macros are only
necessary when you wish to extend the language itself. They tend to be
more complex to write and to debug, although fortunately are easy to
use. Compare, for example, the definition of
declare-classes to that
of the functional equivalent which uses strings.
(defmacro declare-classes [& names] `(do ~@(map (fn [x#] `(defclass ~x#)) names))) (defun f-declare-classes [& names] (dorun (map #(owlclass x) names)))
Even in this case, there is more hieroglyphics in the macro --- two
backticks, one unquote splice and some
symbols although Clojure's slightly irritating lazy sequences and the
dorun mean that the two are nearly as long as each other. I
suspect that the macros are going to get more complex, however. In most
cases, should not be the user of the library that has to cope though.
While this provided a useful convenience, I also wanted a cleaner method for declaring disjoints. Consider this example:
(defclass a) (defclass b) (defclass c) (disjointclasses a b c)
This is reasonably effective, but a pain if there are many classes, as
they all need to be listed in the
disjointclasses list. Worse, this is
error prone; it is all too easy to miss a single class out, particularly
if a new classes is added. So, I have now implemented an
macro which gives this code:
(as-disjoints (defclass a) (defclass b) (defclass c))
This should avoid both the risk of dropping a disjoint, as well avoiding the duplication. An even more common from is to wish to declare a set of classes as disjoint children. Again, I have provided a macro for this, which looks like this:
(defclass CheeseTopping) (as-disjoint-subclasses CheeseTopping (declare-classes GoatsCheeseTopping GorgonzolaTopping MozzarellaTopping ParmesanTopping))
Although this was not my original intention, these are actually nestable. This gives the interesting side effect that the ontology hierarchy is now represented in the structure of the lisp. Example below is an elided hierarchy from pizza. Lisp programmers will notice I have rather exaggerated the indentation to make the point.
(as-disjoint-subclasses PizzaTopping (defclass CheeseTopping) (as-disjoint-subclasses CheeseTopping (declare-classes GoatsCheeseTopping)) (defclass FishTopping) (as-disjoint-subclasses FishTopping (declare-classes AnchoviesTopping)) (defclass FruitTopping) (as-disjoint-subclasses FruitTopping (declare-classes PineappleTopping)))
Of course, it is not essential to do this. The nested use of
as-disjoint-subclasses confers no semantics; but it does allow
juxtaposition of a class and it's children.
Being able to build up macros in this way was the main reason I wanted a real programming language; those described here are, I think, fairly general purpose; so, this form of declaration could also be supported in any of the various syntaxes, although it would require update to the tools. However, some ontologies will benefit from less general purpose extensions. These are never going to be supported in syntax specification.
Still, it is not all advantage. Using a programming language means
embedding within this language. And this means that some of names I
would like to use are gone;
http://clojuredocs.org/clojure_core/clojure.core/some [some] is the
obvious example. While Clojure has good
namespace support, functions in
clojure.core are available in all other namespaces; like all lisps,
Clojure lacks types which would have avoided the problem. There are
other ways around this, but ultimately clashing with these names is
likely to bring pain; for example, I could always explicitly reference
clojure-owl functions; but writing
owl.owl.defclass rather than
defclass seems a poor option; hence,
some has become
comment has become
owlcomment. I have decided to accept the lack of
consistency and kept
label; the alternative, taken by the
OWL API to appending OWL to everything
seems too unwieldy.