clj.orcery

Language, Expression and Design

Tuesday

12

November 2013

Why exceptions should cascade like stylesheets

by Chris Zheng, on ribol, application design

I watched The power of abstraction by Barbara Liskov a while back and was most interest in the description of her work on the ARGUS language. She talked about a neat expection handling system that she had come up with whilst designing the language. The mechanism was designed around a couple of observations that she had about exceptions:

  1. Some exceptions must terminate the program (system level bugs)
  2. Some exceptions should not terminate the program (io/databases connection)
  3. Some exceptions require upper level intervention to decide whether to terminate or not (multi-level systems)

What she did then was to provide semantics to deal with each class of exceptions. A scan of the manual reveals more in-depth information. The language is Section 8.3 of the describes the semantics of exception handling in ARGUS:

After the arguments have been transmitted, the activation action performs the handler body. When the handler body terminates, by executing a return, abort return, signal, or abort signal statement, the result objects are transmitted to the caller by encoding them at the handler’s guardian, and committing or aborting the activation action (as it specified).

It it interesting that apart from the familiar return statement, her proceduces have three other types of control flow that are built right within the language itself. Section 11 outlines design of the exception handling mechanism in more detail and also introduces the resignal statement, which escalates a raised signal to higher level functions if an except statement does not know what to do with it. What was more, the signals preserved the stack; which means that the signal handler could do its thing and then jump straight back into the scope at which the signal was raised.

It occured to me after watching the talk that I have not programmed in this way because the languages I have previously used never gave me the facilities to do that. In order to truly make use of the paradigm, exception handling has to be built into the language right from the ground up. The ARGUS mechanism was a simple, straight forward approach to dealing with multi-tiered code complexity. This is really just common sense. It was much better than the try-catch code that I had been writing all my life. I realised something then and there.

I wanted what she was having.

Conditional Restarts

I had heard of those nebulous clouds of code called conditional restarts. They were all the rage in Common Lisp and people who used them swore by them. It didn't quite click for me until I realised that the ARGUS system WAS a conditional restart system. I then became obsessed. I asked around to find out what others had done for Clojure. In the process, I also found a library called swell which is used in the ritz debugger toolchain; as well as conditions, another great conditional restart implementation for clojure.

However, I was still curious to see if I could implement my own... After all, it felt like any Common Lisp programmer worth their salt had implemented one for practise. However, looking at Common Lisp implementations really didn't help... especially as I don't know Common Lisp.

As I went deeper into the texts, I realised that there was so many alternative strategies for error handling. In a sense, error handling should really be called control flow because what it gives is an additional way for top-level functions to marshall alternative behaviours of lower-level functions without writing code to directly couple the two together. Through more reading, I found a very old library called error-kit by Chris Chouser written in the early days of clojure. There was also a 2009 article by Stuart Halloway showing people how to use it. It surprised me why no one had kept going with the idea. I guess there were more important things on everybody's plate.

I was getting really desperate for a restart library and so took some time studying the error-kit source code. The error-kit source code was very short and became more understandable the longer I stared at it. Using the architecture of error-kit as my base, as well as taking inspiration from the ARGUS language, I implemented my own - ribol. The following is a brief discussion of the principles guiding the library's design.

What is an exception?

As I read more into the implementation details of conditional restart systems, I realised that there was so much I didn't know about error handling. I felt like I was lied to by Java, Python, C#, C++ and all those languages that never gave me an alternative besides the ubiquitous try/catch statement. We never really got the chance to understand what an exception really is:

  1. What constitues an exception?
  2. How should exceptions affect normal program flow?
  3. How should exceptions behave in large multi-tiered applications?
  4. How should exceptions be represented?
  5. How should exceptions be resolved by the system?

exceptions as types

Being familiar with java-styled try/catch paradigm, these questions may seem very obvious:

  1. An exception is an object (typically a class) that comes after the throw statement.
  2. An exception escapes the scope of the function in which it is thrown
  3. An exception at the bottom level has to be caught by a top level function
  4. An exception is represented by a typed object
  5. An exception is resolved using catch statements.

However, the more I really thought about it, the more this paradigm did not make sense to me anymore.

I don't know who came up with the idea of only representing exceptions as a class hierarchy (I'm looking at you Java). Giving me an error type doesn't really help. It's like when I ask someone to - "Please bake a cake for me", and they say: "No I can't, there is a fire problem". It doesn't really help me understand the situation at all. Is it because we can't fire up the oven? Is there a fire at home? Is there a fire at the shop?

Depending upon my expertise in cake-making, fire-fighting or oven-fixing, I would want to know any/all of the following in order to advise how to remedy the situation:

  • at what stage of the cake making are the problems occuring?
  • what are the ingredients missing/currently avaliable?
  • if it is a building fire, what street is it on?
  • if it is an oven problem, who is the manufacturer?
  • what additions options do I have to remedy it?

exceptions as maps

Clojure has the right idea with the clojure.lang.ExceptionInfo object and the ex-info constructor. The thrown exception not only has a message, but also a map representing the exception:

(ex-info "There is a fire problem"
 {:type  :fire/building
  :stage :preparation
  :address "123 Hello Dr"
  :missing [:chocolate :milk]
  :available [:flour :water :eggs :butter]})

So now we have much more information about our exception. We can then use this to give to the fire department as well as to use this information to ask for another recipe.

exceptions as control flow

This is better, but still not good enough. What if the person was already in the middle of baking the cake and then the gas suddenly shut off. I wouldn't want them throwing away the whole cake, I would actually want the cake brought to me so that I can either say:

  • it its pretty much done, continue on
  • or don't worry, I'll take care of it

What if we had told them to bake 100 cakes and the same problem/problems happened? We definitely don't want the person throwing out all the cakes. We would like to say..

  • don't worry about the cake, I'll take care of it and here is a brand new oven I ordered for you. Please continue baking.

This is where traditional Common Lisp styled conditional restart systems shine. With the continue and continue-with statements, that sort of control-flow strategy is possible. Conditional restarts also work amazingly well with recursion. Below is a trivial example of a recursive function that can output if it is inside a manage block.

(defn factorial-ex [n]
  (if (< n 1) (raise :less-than-one)
    (* n (factorial-ex (dec n)))))

(factorial-ex 5) 
=> (throws :less-than-one)

(manage
  (factorial-ex 5)
  (on :less-than-one [] 1))
=> 120 

For those with a bit of time, the ribol documentation is a worth going through as it provides a comprehensive guide on the common use cases, advanced strategies, as well as the implementation details of the library.

exceptions as language

Still, we as programmers can do much much better - above all, we can all buy into the idea and start building with it. Conditional restart systems are not just for the theoretical. They are in fact very, very practical. However, it order to truly see its true magnificence, we have to buy into at a grassroots level so that we use it to build systems with this principle in mind FROM THE GROUND UP.

Interested? Then please read on.

Cascading Errors

When I look at a exception stacktrace, it reminds me of a DOM xpath that has been turned inside out.

Here is an xpath for a html element:

body > #content > #work-of-art > img

Here is a causation path for an exception:

ImageNotFound > ShowWorkOfArt > ShowAllContent > MainBody

The two are remarkably similar. In the xpath, we start at the top and drill down into our elements. In the causation path, We start from the cause of exception, going through the names invoked functions, leading all the way up to the very top of the stack.

There is definitely a difference between how html elements are used and how exceptions are used. However exceptions and dom elements also have alot in common:

  1. They can be represented spacially through xpaths/causation paths.
  2. Their data can be represented by a map (stylesheets in the case of dom elements).
  3. Their data allows the browser or higher-tiered functions to decide what to do with them.

Stylesheet are amazing powerful things. They are essentially maps that define properties on elements for browsers to render. Styles are typically added to classes. A class is also quite a nebulus concepts. A class can literally mean anything, and is typically used to give semantic meaning to html elements.

Here is a typical example of how stylesheets are used:

    <style>
      .menu{background-color: red; height: 10px},
      .active{text-decoration: underline}
    </style>

    <ul class='menu'>
      <li class='active'>Item One</li>
      <li>Item Two</li>
    </ul>

We define our styles for the menu class the active class and then The html elements can then be tagged by the class tag and displayed on the page.

This is a very simple example. The scope of CSS is too broad to get a really good understanding of it. So much so that there are now so many books coming out with design patterns for CSS. There are a whole bunch of acronyms - smacs, omacs - as well as a compiler in every language.

For a while there, I was really scared of looking at CSS because I just did not understand it. I now know a little bit more... and then more CSS I write, the more I am convinced that one day, we will all be programming in some sort of CSS/Prolog hybrid language.

Huh?

One might start asking at the point in time. "How did we get onto CSS? I thought we were talking about exception handling."

Well, lets see. CSS allows the markup of DOM elements by allowing specification of properties using a combinatorial style of application. Multiple CSS classes can be defined:

    <style>
     .red{background-color: red};
     .small {height: 10px;};
     .bordered {border: 1px solid black};
     .underlined{text-decoration: underline}
    </style>   

and applied in combination

    <section class="red bordered">
      <div class="small underlined">Hello World</div>
    </section>

An important attribute of CSS is the ability of styles with greater specitivity to overwrite styles that are more general. So adding:

    <style>
       div.small {height: 5px;};
    </style>   

Will change how the Hello World looks because div.small is more specifc to the <div class="small underlined">Hello World</div> element than .small. This type of programming allows composition of abitrary properties and is very powerful if used well. However usually it is not and people tend to shoot themselves in the foot (much more so than with C++). This is this property of CSS that gives the DOM so much flexibility in the way elements can be controlled, manipulated and displayed. It also gives context to the elements on the page, so that a web designer can have the power to isolate certain elements as well as to target a more general class of elements.

Contextual Issues

Exceptions can be thought of as an html element, the data within exception as the style. For instance, I have a function chk-string.

(defn chk-string [input]
  (if (string? input) input
    (raise [:input-not-string {:input input}])))

If the input is a string, it will continue. However, it the input is not a string, then it will raise an :input-not-string issue.

The mid-level function chk-valid-name uses chk-string and performs an escalation statement on any raised issues.

(defn chk-valid-name [input]
  (manage
    (chk-string input)
    ....
    (on _ []
      (escalate :chk-valid-name))))

In another mid-level function, chk-valid-business-name also uses chk-string and also performs an escalation statement on any raised issues.

(defn chk-valid-business-name [input]
  (manage
    (chk-string input)
    ....
    (on _ []
      (escalate :chk-valid-business-name))))

This type of escalation cascade can go on for a couple of layers of functionality. Lets stop there for now and define a top-level function that used both chk-valid-name and chk-valid-business-name:

(defn registration [input]
  (manage
    (cond
       ... (chk-valid-name ...)
       ... (chk-valid-business-name ...))

    (on :input-not-string e 
       (cond (e :chk-valid-name)
             (.. do something...)

             (e :chk-valid-business-name)
             (.. do something...)
             ))))

This is now already quite a powerful system for debugging. In this example, we can now isolate the exceptions coming from each of the two middle tier functions and log/process/analyse them in greater detail. We might only care about having a valid name and not so much a valid business name and only handle those things in detail.

If we can imagine that there were more tiers of functionality added to the system, it may very well resemble something like the DOM. The three important aspects of a dom element correspond to three aspects of an exception:

  • the xpath -> causation path
  • the stylesheet -> exception data
  • the page -> final program behavior

By understanding exceptions in this light, we see them not as bugs to be caught but as tools that help us understand our programs better and with more precision. I believe that more systems should be designed using this principle and this should make our large applications more debuggable, more robust, more fault-tolerant and more maintainable in the long run.

comments powered by Disqus