I’ve been listening to Roy’s music for years: originally, I read an article in a guitar magazine, and then heard down the grapevine about his live peformance, and I thought it sounded fun. This all happened when I was about to go to university. It was this collection of circumstances that has meant that Roy had a formative place in my musical upbringing. I bought a copy of his album Once, and shortly after saw him play live. Before this, my life music had been limited to blues in the local pubs; Roy was the first “real” gig that I saw live.

After this, Roy along with John Martyn (http://www.russet.org.uk/blog/667) (who was responsible for the second real gig I went to), became a regular. I would see him play every year or two, I bought a lot of his back catalogue and listened to it often.

As time has moved on, Roy has got older and tours rarely now; I last saw him in 2010 (as support for Joanna Newsome). And I listen to his music much less, largely because I listen to music much less. I decided to go down south, therefore, partly for old-time sake. I wanted to hear him live again.

Despite the passing of the years, he remains fantastic: he voice still has a grandeur and sweep to it. His songs are passionate, moving and his lyrics poetic. Over time, of course, his performance has changed: no longer the distorted guitar, nor using his mic stand as a slide, the full “One Man Rock and Roll Band”. His first song was about reminiscance, and this was perhaps appropriate; the strongest of his music is now in his gentler, more melancholic, quieter songs.

The strange thing about the gig, though, was my reaction. When he started to talk about the next song, I would often recognise bits of the intro, but then could not remember the song associated with it. But when he started playing, I knew the tune, and the words, sometimes without remembering what the song was called, it would slowly uncover in my mind.

Music is part of life. It has always evoked many emotions: the excitement when you discover new music, the passion when you listen it repeatedly, the sense of dislocation when the music lifts you away from yourself and takes you to a different place. Of course, it is not just Roy getting older: I am about the same age now as Roy would have been when I first saw him. Music can be many things, but now, as I age, I find a new emotion: the pleasure of association, with music that I have heard and changed with over time. I have learned it so well that, even where I cannot remember it, I will never forget it entirely. In that sense, it has become and will remain a part of me.

Bibliography

I am very happy with myself as, on saturday, I managed to complete the 64 mile Cyclone. The last time I managed this was 6 years ago (http://www.russet.org.uk/blog/1697). There were other momentus events happening in my life that year (http://www.russet.org.uk/blog/1846), which probably explain why I have not found the time to replicate the feat since then.

My main change this year has been my training schedule; I knew that I was going to get there by cycling at the weekends — it’s not enough, and too irregular. So, instead, I have been using some cycle rollers that I bought ages ago but have not really just started to use. And what a difference they have made; I remember feeling tired after a 20 mile ride in April, so have got to 60 pretty quick. In fact, I managed to time close to my previous best, coming in at 4:57 (on the road) or 5:12 (elapsed) which makes just over 12mph.

It was a hard ride — I’ve been training mostly up and down the river, and the cyclone is pretty hill; I was starting to feel tired at 50 miles and, although I did keep the speed up for the ride in, I was reaching my limit. Having said that, this year, they have added a 90mile route as well, and I fancy trying it next year. I’m not totally convinced that my bike (http://www.russet.org.uk/blog/1202) is right for it. It’s a little heavy, probably about 15kg with all the extra baggage I carry on it. But, then, I am also a little heavy with the extra baggage I am carrying, so perhaps it’s not an issue.

Bibliography

I have written about assess previously (http://www.russet.org.uk/blog/3135); it is a tool which provides predicates, macros and functions to support testing for Emacs. It is actually agnostic to the test environment, although has specialised support for ERT.

My new release of assess (v0.3.2) includes one significant change, and two new features. I have updated the call capture functionality — the first version stored all the call data in a global variable, which was quick and easy, but clearly not a log term solution. It now uses closures instead which means that several functions can be captured at once. This also allows the first new feature, which is the ability to capture calls to hooks, with the function assess-call-capture-hook, which takes a hook and a lambda, and returns any calls to the hook when the lambda is evaluated. As an example usage, from assess-call-tests.el:

(should
 (equal
  '(nil nil)
  (assess-call-capture-hook
   'assess-call-test-hook
   (lambda ()
     (run-hooks 'assess-call-test-hook)
     (run-hooks 'assess-call-test-hook)))))

This is good functionality and should be very useful. The API could be improved a bit; a macro version would avoid the explicit lambda, for example. And returning a list of nil means this function also works with hooks with args, but is a bit ugly for hooks without (which are the majority).

The second area that I wanted to address has come about because of my hacking into the Emacs undo system. This is hard to test automatically; I have often found myself writing things like this test from simple-test.el.

(should
 (with-temp-buffer
   (setq buffer-undo-list nil)
   (insert "hello")
   (member (current-buffer) undo-auto--undoably-changed-buffers)))

This is okay, but it’s painful to write; I am trying to robotize Emacs, and it’s not easy. Some times it’s hard to work out exactly what set of functions you need to call. It would be much easier just to type a key sequence and have Emacs run this for you.

Fortunately, Emacs has special support for this in the form of keyboard macros; you can remember, store and save any set of keypresses and run them, rerun them, automate them or, most importantly, save them to a file as a lisp expression. This example, for instance, comes from viper-test.el.

(kmacro-call-macro nil nil nil
                   [left
                    ;; Delete "c"
                    backspace
                    left left left
                    ;; Delete "a"
                    backspace
                    ;; C-/ or undo
                    67108911])

This is okay, but it’s still not ideal. I have had to add comments to make the test clear by hand. It’s not easy to read, and what is that 67108911 about? It comes from somewhere in Emacs and is stable into the future. But, you only have my word for it that this is undo. It would be all too easy to get this wrong, to have the wrong comment. Tests need to be readable.

Fortunately, Emacs provides a nice solution, in the form of edmacro — this is a major-mode for editing macros after they have been created. It also defines a human readable version of a macro. We can parse this and then execute it directly. This example comes from simple-test.el.

(execute-kbd-macro
  (read-kbd-macro
        "
C-c C-o                 ;; latex-insert-block
RET                     ;; newline
C-/                     ;; undo
"))

The advantage of this is that I didn’t actually write this string; I recorded the macro, the edited it and copied the contents of the edmacro buffer.

This is still not easy enough, though; I want an easier way of editing the macro as it appears in the test. This is, unfortunately, difficult as the edit-kbd-macro is not easy to invoke programmatically — it absolutely hard-codes user interaction (I did even try invoking edit-kbd-macro using a keyboard macro!). So, I have given up with that approach in the short term. Instead, I have written a function assess-robot-execute-macro that combines read-kbd-macro and execute-kbd-macro, but also sets the macro as the last macro, making it easy to edit. I’ve also added a keybinding to edmacro to copy the macro to the kill-ring. And here is a test using it:

(should
 (assess=
   "hello"
   (assess-robot-with-switched-buffer-string
     (assess-robot-execute-kmacro
"
hello                   ;; self-insert-command * 5
"))))

This also demonstrates the final quirk. Keyboard macros work in which ever buffer is selected — not the one which is current. We cannot use with-temp-buffer to select on temporarily and run the macro in it. So I have added macros to display a buffer temporarily instead.

As with many parts of assess, the back end is quite convoluted and complex, as many parts of Emacs were not written with testing in mind (that is they predate the whole idea of unit testing by many years!). But, I hope that the API that assess provides is simple, clear and flexible.

Assess is available at github and MELPA.

Feedback is, as always, welcome.

Bibliography

Assess is my new package supporting testing in Emacs. It has grown out of my frustration with the existing framework while building the lentic package (http://www.russet.org.uk/blog/3071).

For quite a while, the only testing framework in Emacs has been ERT (the Emacs Regression Testing tool) which is part of core. More recently, there have been a number of new ones arriving. For example, buttercup and ecukes both provide behaviour driven testing, rather like Jasmine or Cucumber respectively. Both worth looking at — I’ve used Ecukes for testing Cask, and it’s nicely implemented and very usable. Assess is rather less radical than this though. It focuses on providing a general set of tools for testing, mostly in terms of some macros and predicates that should be useful.

For example, a recurrent problem during the development of lentic was ensuring that no buffers were left around after a test. Particularly when a test fails, this can lead to unexpected failures in later tests. For this purpose, I have added a macro called assess-with-preserved-buffer-list. Any buffers at all created inside this macro will be removed after. Hence this:

(assess-with-preserved-buffer-list
  (get-buffer-create "a")
  (get-buffer-create "b")
  (get-buffer-create "c"))

Which preserves the buffer state. a, b and c will be removed after the macro exits.

Assess also provides some handy predicates for testing. So, we can compare the contents of strings, buffer and files easily. For example:

;; Compare Two Strings
(assess= "hello" "goodbye")

;; Compare the contents of Two Buffers
(assess=
  (assess-buffer "assess.el")
  (assess-buffer "assess-previous.el"))

;; Compare the contents of Two files
(assess=
  (assess-file "~/.emacs")
  (assess-file "~/.emacs"))

Again, this has all been done carefully to avoid changing state. The last example, should work whether ~/.emacs is open already or not, and will not result in new buffer creation.

As well as string comparison, we can also check that indentation is working correctly. This example, for instance, takes a indented string, un-indents and re-indents according to a specific mode, then checks that nothing has changed.

(assess-roundtrip-indentation=
  'emacs-lisp-mode
  "(assess-with-find-file\n    \"~/.emacs\"\n  (buffer-string))")

Likewise, we can check fontification with the assess-face-at= function. In this case, we are checking that three words get highlighted correctly.

(assess-face-at=
 "(defun x ())\n(defmacro y ())\n(defun z ())"
 'emacs-lisp-mode
 '("defun" "defmacro" "defun")
 'font-lock-keyword-face)

Finally, in terms of test functions, I have recently added two new pieces of functionality. Call capturing was an idea I stole from buttercup — this is a non-interative equivalent to the function tracing, returning parameters and return values to a function call.

(assess-call-capture
  '+
  (lambda()
    (+ 1 1)))
;; => (((1 1) . 2))

And discover provides a drop in replacement for ert-run-tests-batch-and-exit except that it automatically finds and loads test files, based on a set of heuritstics. I’ve already started to use this instead of ert-runner, as it requires no configuration.

The final thing I wanted to address was better reporting. While everything described so far as test environment agnostic, I’ve only managed to advance reporting for ERT. All of the functions that I have written plug into ERT, so produce richer output. So instead of:

F temp
    (ert-test-failed
     ((should
       (string= "a" "b"))
      :form
      (string= "a" "b")
      :value nil))

When comparing "a" and "b", this output is fine, but if the strings are more complex (say, for example, the a short piece of code that you expect to indent in a certain way), it is hard to work out what the differences are.

So, assess extends ERT so that that it now calls diff on the strings. We now get this explanation instead:

F test-assess=
    (ert-test-failed
     ((should
       (assess= "a" "b"))
      :form
      (assess= "a" "b")
      :value nil :explanation "Strings:
a
and
b
Differ at:*** /tmp/a935uPW      2016-01-20 13:25:47.373076381 +0000
--- /tmp/b9357Zc        2016-01-20 13:25:47.437076381 +0000
***************
*** 1 ****
! a
\\ No newline at end of file
--- 1 ----
! b
\\ No newline at end of file

"))

Verbose for sure, but very useful for identifying issues, especially white space related. By default, this uses the diff command, but this is also extensible, and has a simple fallback in its absence.

There is more functionality to assess than that shown here: it can create multiple temporary buffers in a single scope; it can create “related” temporary files, prevent conflicts if files are already open; it can re-indent the contents of files rather than buffers, and so on. I think assess is a nice addition to the testing capabilities of Emacs.

More is needed and some of this involve changes to Emacs core — packages which are noisy, enforce interactivity, and so forth. I would also like to add support for “robotized” tests running keyboard macros, tests for checking hooks, output to the message buffer, and testing for asynchronous call backs. But assess is quick, easy to use and makes testing of many features of Emacs much easier. I’ve started to use it in my own packages, and will eventually use it in all of them.

My plans for the future are to move assess to ELPA, and then eventually to Emacs core after 25.1, probably as ert-assess. I hope that along with my restructuring of the Emacs unit tests files, this will make testing of Emacs core simpler and more straight forward; if it does this should make the merge hassles (mostly to other people, sadly) caused by file moves worthwhile. Emacs will be easier to develop, and simpler to change.

Feedback is, as always, welcome.

Bibliography

Over the years, a great deal has been written about the ontology of pizza (http://robertdavidstevens.wordpress.com/2010/01/22/why-the-pizza-ontology-tutorial/). It’s a good example, is easy to understand and works surprisingly well in a tutorial context. It is also comes up surprisingly commonly in the public sphere as it did last year on BBC News (http://www.bbc.co.uk/news/magazine-33542392). The key point of which is this: the pizza maker argues that you can’t have a marinara (tomato and garlic) with added mozzarella because a marinara is pizza rossa which can’t have mozzarella; a margherita (tomato and mozzarella) with garlic is fine though. Ha, those crazy Italians. I paraphrase, of course.

Of course, the right to comment first on this article rests with Robert Stevens, my colleague and world acknowledged authority on the ontology of pizza, and indeed he has done so (http://robertdavidstevens.wordpress.com/2015/08/13/where-a-pizza-ontology-may-help/).

I would like to take a slightly different approach to the question though. How do we know what our ontology should be? I’ll start by ontological reading of the article, look at some issues with it, and then consider how we might gather the knowledge to resolve them. As with many parts of ontology construction, there is not a perfect answer and it turns out to be more complex than it might appear at first sight.


A Literate Approach

I start by building an ontology. As (both) regular readers of this journal might expect, I am going to use Tawny-OWL (http://www.russet.org.uk/blog/3088) to do this, so the syntax is a little different than for Robert’s post.

I’m starting from a slightly different place from Robert. One of his concerns was to fit within the context of the existing pizza ontology. Now, I use to be a firm believer in the idea that ontologies were all about a shared conceptualisation of the world, but over time, I have become less sure that this should always be a consideration. In this case, the purpose of building the ontology is to allow me to formally and explicitly describe something with the context of this blogpost, to clarify my own understanding. Sharing is irrelevant for this use case and I am going to build my ontology rapidly from scratch.

I am a big fan of literate ontologies (1512.04250) and I want do that with blogs as well. So the source code of this post (accessible from http://archive.org/download/phil-lord-journal/), is lenticular text (http://www.russet.org.uk/blog/3035) and the whole article can be parsed as a valid ontology. Perhaps no change for the reader, but a comfort for me as the author to know that I’ve evaluated every statement in this post.

(ns the-epistemology-of-pizza
  (:refer-clojure :only [])
  (:use [tawny owl english reasoner]))

(reasoner-factory :hermit)

As with all Tawny-OWL ontologies, we start with a namespace declaration. If you are reading this and use Clojure a lot, note the :refer-clojure and use of tawny.english: none of or, some or not are the clojure.core functions! We select a default reasoner also.

(defontology epistemology)

(defclass PizzaTopping)
(defoproperty hasTopping)

(as-subclasses
 PizzaTopping
 :disjoint
 (defclass Mozzarella)
 (defclass Tomato)
 (defclass Garlic))

We need a set of primitive terms on which to base the ontology, which we define here. Again, this is not an ontology for sharing: I have not added labels, formal textual definitions nor do I need to care about what the IRIs actual are. We only need three toppings and all we need to care about them is that they are different.

(defclass Pizza
  :super (some hasTopping (or Mozzarella Tomato)))

It turns out, I do not need to care about pizza bases either, so I am not going to talk about them explicitly. Rather unusually (and in a difference from the pizza ontology) I am going to insist that a Pizza have either Mozzarella or Tomato. I’ll come back to this later.

(defclass PizzaRossa
  :super
  Pizza
  (only hasTopping (not Mozzarella)))

(defclass PizzaBianca
  :super
  Pizza
  (only hasTopping (not Tomato)))

The definitions of PizzaRossa and PizzaBianca are a little surprising; they are defined negatively, but I quite like these definitions. They are sort of like the backward definitions of the fly geneticist — the “white” gene is responsible for the enzyme that causes red eyes. The gene is named after the mutant.

(defclass Marinara
  :super
  PizzaRossa
  (some hasTopping Garlic Tomato))

The definition of Marinara is now straight-forward enough, simply stating the ingredients, and that Marinara is a PizzaRossa.

(clojure.core/assert
 (with-probe-entities
   [WierdMarinara
    (owl-class "WierdMarinara"
               :super Marinara
               (some hasTopping Mozzarella))]
   (clojure.core/not (coherent?))))

And, so we get to the humourous crux of the story which is, indeed, you cannot have a Marinara with Mozzarella.


The Epistemological Questions

But this leaves us with a number of problems. One of which is that in explaining the joke, we have rather killed it; one sad reality that all ontologists have to face is that it pushes us toward pedantry, making us humourless, crushing bores. A much deeper problem though is that what we have produced is a computational data structure which we have queried and got an answer about that computational data structure. When what we really want to know about is pizza.

How do we know whether what we have modelled is correct. Is this ontology a good ontology, an accurate reflection of reality? And what does this mean anyway? In short, how do we know what we know?

Let’s consider the issue from a set of different perspectives.

The Software Engineer

As a software engineer, I’m rather fond of the ontology that I have produced, and in that sense it is a good ontology. I’ve already said that I quite like the “backward definitions”, and find them quite elegant. The ontology is also symmetrical: PizzaBianca and PizzaRossa are both defined in the same, quite equivalent way.

Now, of course, we might argue that this considerations do not tell us that we have a good ontology. But an elegant and symmetrical axiomatisation is useful; it’s easier to remember, there are no “special cases”, and it is easy to spot outliers. All of these things support maintainability of software in general and specifically in ontologies.

There are some issues with my ontology; we have not, for instance, following Alan Rector’s normalisation pattern (http://ontogenesis.knowledgeblog.org/49); the Margherita is neither white nor red in this schema.

Margherita presents a bigger problem, though, than not being normalised, which is simply that my knowledge of margherita tells me that it is normally considered a pizza rossa, while my ontology say it is not. My ontology is nice, but it is wrong.

The philosophical approach

We could also consider this ontology from a more philosophical point-of-view. Of course, I am perhaps not the ideal person to do this, but I would note that our ontology has a clear, single inheritance hierarchy. Most of our classes have clear differentiatia (I’ve just stopped at the toppings, but you can’t define everything, because that turtles (http://en.wikipedia.org/wiki/Turtles_all_the_way_down)). Even our definition of pizza could fit into a larger hierarchy: pizza without either mozzarella or tomato is either a focaccia or some other type of bread.

After that, I am rather stuck. It is hard to draw many more conclusions about pizza from first principles. From a realist point-of-view, we should model reality and universals, which sounds nice. But how do I determine that what that reality is?

Time to phone a friend.

The Expert Analysis Technique

One standard technique in ontology building is to consult with an expert. Indeed, that is often the main evidence and justification that is used to support an ontology, which is why many ontology papers have more authors than the human genome paper. So, let’s try it in this case. The BBC article quotes from quite a few experts.

“La marinara is a pizza rossa,” she states frostily. “A pizza rossa is made with tomato and without mozzarella. So you can’t have a marinara with mozzarella because there’s no such thing.”

Emanuela
— BBC

My ontology supports this because Marinara is a PizzaRossa so, indeed, cannot have Mozzarella.

“No, it’s not,” pipes up a customer who until now has been quietly consuming his pizza and beer on a stool behind me. “She’s right. A pizza rossa can’t have mozzarella.”

Customer
— BBC

Also!

The pizzaiola is right. A marinara is not a marinara if you add mozzarella. But she was wrong to say she would make you a margherita with garlic because margherita with garlic doesn’t exist.”

Friend
— BBC

Currently, my ontology says nothing about this one way or the other. But, we can add a closed definition for Margherita easily, and now, indeed, a margherita with garlic cannot exist.

(defclass Margherita
  :super Pizza
  (some-only hasTopping Mozzarella Tomato))

(clojure.core/assert
 (with-probe-entities
   [WierdMargherita
    (owl-class "WeirdMargherita"
               :super Margherita
               (some hasTopping Garlic))]
   (clojure.core/not (coherent?))))

I also tried extending my analysis further, with a novel technique; I tried it by asking my friends on facebook. One of them, replied as follows.

my take on this is that we must distinguish between pizzas made by bakeries (pizza bianca and rossa) which tipically (but theres no firm rules) do not have mozzarella, and pizza from a pizzeria (restaurant) which almost always has mozzarella (bianca and rossa). a pizza bianca without mozzarella is a focaccia ( with rosemary, onions, potatoes, etc)

— Ulisse Pizzi

It’s a disaster! Almost, none of this has been modelled in my ontology, although it supports my assertion that a pizza must have tomato or mozzarella. But worse, “there are no firm rules”; it’s enough to make a grown ontologist cry. But there are some deeper frustrations here. None of the experts have told me all the issues that I want to know. None have really answered what is a pizza.

We could ask some more experts? But what happens when they contradict? Do we take averages? And, more important, how do I know when to stop; I could carry on asking friends all day. And how do we avoid cherry-(tomato)-picking?

From the Definitive sauce (erm, source)

Having tried ontology by facebook, let’s try a research method with a much older and richer pedigree; we will look the answer up on wikipedia instead. First port of call is the main Pizza, which says many things, but none of them that useful in this case. Hunting around got me to Pizza al taglio (pizza by the slice) which says:

The simplest varieties include pizza Margherita (tomato sauce and cheese), pizza bianca (olive oil, rosemary and garlic),[4] and pizza rossa (tomato sauce only).

— Pizza al taglio

This is interesting becuase it brings in garlic and rosemary (which has not been mentioned before). In this version of the world, a margherita is not a pizza bianca. And there is a lack of symmetry between rossa (which is closed) and bianca (which is not).

Perhaps a better source of material would be to look on the Italian wikipedia for its definition of Pizza. This says:

Pizza marchigiana […] Le varianti tradizionali sono quattro: bianca[12] con il rosmarino, bianca[12] alla cipolla, rossa semplice[13] e rossa[13] con la mozzarella.

Pizza marchigiana
— Italian Wikipedia

So, more types of pizza, including with rosemary, with onion and simple. The footnotes are more informative still.

[12] Per “bianca” si intende una pizza senza pomodoro. [13] Per “rossa” si intende una pizza con il pomodoro.

Pizza
— Italian Wikipedia

So, pizza bianca must NOT have tomato, while rossa MUST have it. The definition of pizza rossa here is inconsistent with mine, but makes more sense to be honest — margherita finally becomes a pizza rossa. Strictly, this quote only really applies to “pizza marchigiana” — once we move out of Marche, all our definitions might be different!

Another useful thing that does come out of reading wikipedia, however, is the information that Pizza Neopolitana has regionally protected status in the EU. And that there is an official body for defining what is a pizza, namely the AVPN. Their document describing the pizza is called the disciplinare (also in English).

And, indeed it has definitions, although unfortunately only of two pizza, the Marinara and the Margherita. So, the answers we get from here are limited; but they look like this:

Marinara (tomato, oil, oregano, and garlic) Margherita (tomato, oil, mozzarella or fior di latte, grated cheese and basil)

Disciplinare
— Associazione Verace Pizza Napoletana

So, we have our answer? Well, it’s one I find surprising (grated hard cheese on margherita!). But, actually, the disciplinare is more specific. For instance, where they say tomato not just any tomato will do, they have to be one from a very specific type:

The following variations of fresh tomatoes can be used: “S.Marzano dell’Agro Sarnese-nocerino D.O.P”., “Pomodorini di Corbara (Corbarino)”, “Pomodorino del piennolo del Vesuvio” D.O.P.” (see attached appendices for suppliers and technical details)

Disciplinare
— Associazione Verace Pizza Napoletana

How can we encode this? It really just too complicated, and is unlikely to be useful ontologically at any point; worse, we only have answers for two pizza types and there many others. Looking further, there are also instructions for the flour, the proving, the stretching and much more besides. So, it turns out that the definitive answer is not that useful either: it is incomplete and overly complicated.


Conclusions

The BBC articles take on the whole process is this:

Pizza has taught me that logic can be subjective and that subjective logic can be cultural

— BBC

Of course, it’s not true; logic is not subjective at all, although there are many different forms of logic. But definitions can be. I have tried multiple different mechanisms of reaching a definition, and all of them have flaws:

  • software engineering approach — maintainable, nice but correct?
  • philosophical approach — limited in its applicability
  • find a friend (the facebook approach) — prone to cherry picking
  • literature review (the wikipedia approach) — lacks interactivity, so may not answre the question
  • A definitive source (the authoritarian approach) — over specified, under covering

Definitions are difficult, and there will be no universal answer. Having a clearly defined use case, and a mechanism to test your ontology against that use case remains key. But also having a clear awareness of the techniques that you are using for build your ontology and the flaws that exist with them.

It is also a good excuse to eat pizza, if you need one.


Updates

Minor spelling corrections!

Bibliography

My last blogpost was on logging (http://www.russet.org.uk/blog/3112) with the SLF4J library, its inclusion in the OWL API, and its impact on Tawny-OWL. It raised quite a response, not least because it suggested that people ignore the advice of the authors of the library.

Perhaps, unsurprisingly, the general feeling was against my position — something that I expected given that this had already happened on the SLF4J mailing list. Now, of course, it does have to be said that there is a significant sampling bias here: anyone on the mailing list, or who reads and responds to a post about a logging API, is likely to be interested in logging rather than a passive user (or non-user) of such a library. None the less, I think it is worth reflecting on the reasons for the comments.

Of course, I am aware, that as many people pointed out, the problem is not huge (a three line error message) and my solution is messy. Well, I agree, the solution is messy, but it is the only method that SLF4J has support for. There are alternatives; for example, this commit from the OWL API provides another solution. I have not decided yet whether this is the right way to go or not.

Several people complained that including the nop dependency will silently turn off all logging. Actually, this is not true: in applications which have logging already, including a nop dependency, will trigger an error message; whether logging is turned off or not is hard to determine — it depends on the classpath ordering. I am, however, clearly making life harder for those who want logging on. Specifically, I am requiring that instead of using:

["uk.org.russet/tawny-owl "1.5.0"]

It will require this:

["uk.org.russet/tawny-owl "1.5.0" :exclusions ["org.slf4/slf4f-nop"]]

(or the equivalent for maven). So, harder, but not much harder.

The other two main suggestions are that I split my code into library and application, even though this is artificial (the “application” would currently contain no code). But this approach would require most of my downstream dependencies to also be split into two for the same reason; it was even suggested that the Clojure REPL or Leiningen that should do the work, and that they should be split into two. Introducing such complexity for a logging API is surely not good engineering.

Finally, there were many suggestions that I leave the decision to the “end user”. But, as I have said, while I like allowing the “end user” to have a choice, I do not believe in imposing it on them. Choose a default where it is at all possible and do not sit on the fence.

During this process, though, I have thought a lot about logging, and I have come to the conclusion that the core of the problem is that logging is heavily overloaded. Quite a few people were frankly amazed that I would not want to see logging, asserting that most people would want to see it.

Now, I use a web server, and use its logs all the time. Logs are critical for many reasons, and many tools like webalyser or fail2ban work over them. I would never want to turn these off. But, here, though, the logging is effectily a part of the application: apache logs are part of the adminstration user interface, as well as forming an ad hoc API to downstream tools; part of the plumbing in git parlance. Why would you want to switch this off? I would not.

In this case, though, if logging is part of the application, why on earth are people so keen on libraries logging — the application should not be deciding the location of the logs only, but also how and what is logged. Libraries should just not be logging at all; they should be taking callbacks, returning values and leaving the outcome of this to the application.

What about “error messages” though? Surely libraries can produce these? The OWL API (which is the library that makes use of SLF4J) is an example of this. Most of the logging message are errors: for example, this situation:

if (loadCount.get() != importsLoadCount.get()) {
   LOGGER.warn(
               "Runtime Warning: Parsers should load imported ontologies using the makeImportLoadRequest method.");
}

which is effectively a form of active documentation or this:

try {
    return optional(new XZInputStream(new BufferedInputStream(new FileInputStream(file))));
} catch (IOException e) {
    LOGGER.error("File cannot be found or opened", e);
    failedOnStreams.set(true);
    return emptyOptional();
}

which is sort of an error but one that has actually been dealt with. These are mostly debugging statements that the authors think they may use again. That’s fine and an entirely reasonable use of a logging framework. Equally, and I feel entirely reasonably, I want this off by default.

The “error message” from SLF4J that started me down this path falls into the same category. It is not an error, or it would throw an exception (as it used to do in the past). It is a debugging statement that is there for a normal condition that the authors are, nonetheless, not entirely happy about. There are two reasonable defaults (be noisy, or be silent), and they do not know which to choose.

My previous trip into the field of logging was twenty years ago, when I wrote a fun Emacs packages for putting System.out.println statements into the code (then taking them out again); as one person said of me, I “simply lack the knowledge” of logging frameworks to comment. I am happy to accept that this is entirely correct. Despite this my conclusions are simple. I remain convinced that SLF4J should have the courage of its convictions; if there is an error, signal an error, otherwise remain silent. Until it does this, adding the nop dependency remains the right way forward for me. Take my advice with a distinct pinch of salt, certainly. At the same time, think how many people have been irritated by that error message and not taken the time to comment. If you are worried about these people, then using the nop dependency may be the right way forward for you to.

And there I shall leave the discussion and comment no more.

Bibliography