Monday, April 13, 2009

Conditions, restarts and great people

I've been studying conditions and restarts in lisp for a bit now. I've read the chapter in Practical Common Lisp about it but didn't quite get it right away.
So I asked around in #lisp on freenode for some help. I initially got other links (a tutorial and a paper by Pitman) with a lot of information. The first link assumes already some knowledge of the topic and the second one was such a large read that I was a bit discouraged. "Is this really so hard?", I thought to myself.

I went back to PCL and read the chapter again, this time making the examples in my editor as I went along. (I really should have done that from the start)
Not getting the example to work frustrated me, it sounded great on paper and it was well explained but I just couldn't get it to work.

So I ask on #lisp again, pasted some code to the pastebin and waited for a reaction. A few people initially reacted with 'oh you need restart-case'.
While entirely correct, I didn't get it because I was sure I tried that example and while the restart-case on itself worked, I removed it again when I went over the next examples. Then Peter Seibel (who also idles in the #lisp channel) saw that I was struggling and started giving me pointers on what to re-read in his book. After about half an hour of reviewing the code, I figured it out, fixed the bug and got it to work.

This is always a good starting point when I'm learning stuff. I follow examples and type in the code, see that it works and then figure out why it is working. Peter's examples in his book confused me a bit, the naming of the functions made me lose track of what was happening. (Don't get me wrong, I'm not blaming anybody but myself here) After several tries, I was pretty sure I got it down and knew how to use conditions with restarts.

A couple of hours later, I got a query on IRC from Paul Gresham who gave me a very simple and wonderful example:


(declaim (optimize debug))
(defpackage :condition-fun (:use :cl))
(in-package :condition-fun)

;; This tutorial focuses on very simple restarts. Lisp can do a
;; lot more, but the constructs here are already incredibly
;; powerful.
;; This tutorial does not look at defining conditions as we
;; want to focus only on restarts.

;; Imagine this is your cool API. We have two implementations. The
;; first is typical, nasty code that just sticks Nil in and
;; essentially ignores the error. The second is much better, it
;; provides three restarts for our clients to use.

(defun nasty-fun (data)
"Oh bugger! what to do about div-by-zero. Screw it, just return Nil!"
(loop for d in data collect
(handler-case (/ (random 1000) d)
(division-by-zero () nil))))

(defun much-nicer-fun (data)
"We allow our parent to decide what to do about div by zero."
(loop for d in data collect
(restart-case (/ (random 1000) d)
(use-nil () nil)
(use-zero () 0)
(use-value (value) value))))

;; What this means is that the calling program can effect low level
;; errors inside our API. They can choose what to do about it, to
;; correct the error in a way meaningful to them and continue
;; processing. Don't get stuck here, work through the next steps
;; then you'll understand.


;; Imagine this is some client code using your cool API

(defvar *data* '(2 3 4 0 6 2 0 9))
;; ^ ^ Uh-oh!

(defun critical-fun-that-hates-nil (data)
"We're only hire professionals and expect them to return
us good stuff even if we send them garbage."
(loop for d in data collect
(* (random 1000) d)))

(defun important-programme1 ()
"If this dies the client loses US$1BN."
(critical-fun-that-hates-nil
(nasty-fun *data*)))

;; Ok let's give the option to manually recover. Experiment
;; with the different restarts at the prompt.
(defun important-programme2 ()
"The previous version would lose a lot of money each time
it's run. We need more control"
(critical-fun-that-hates-nil
(much-nicer-fun *data*)))

(defun important-programme3 ()
"Great! Now we have to employ someone to sit there
selecting the restart everytime, lets automate that
with a dynamic function."
(critical-fun-that-hates-nil
(handler-bind ((division-by-zero
#'(lambda (x) (invoke-restart 'use-zero))))
(much-nicer-fun *data*))))

(defun use-zero-please (x)
"Provide a function for the restart for readability purposes."
(declare (ignore x))
(invoke-restart 'use-zero))

(defun important-programme4 ()
"The dev's at the client only write VB code and
can't understand this lambda stuff, make clearer
so they can read our code"
(critical-fun-that-hates-nil
(handler-bind ((division-by-zero #'use-zero-please))
(much-nicer-fun *data*))))

(defun important-programme5 ()
"Wow, our client is happy, so happy, they now want to specify
a their own value as zero is causing them to lose money."
(critical-fun-that-hates-nil
(handler-bind ((division-by-zero #'(lambda (x)
(invoke-restart 'use-value 1))))
(much-nicer-fun *data*))))

;; Oh but it's not readable again. This time we need a function
;; that creates a function for our client. Yes, we're writing
;; code that writes code for them!

(defun use-value-please (value)
"Don't worry they won't know this actually returns a function,
it'll look ok to them!"
#'(lambda (x)
(declare (ignore x)) ;; because this is a simple tutorial!!
(invoke-restart 'use-value value)))

(defun important-programme6 ()
"Allow the value to be specified"
(critical-fun-that-hates-nil
(handler-bind ((division-by-zero (use-value-please 10)))
(much-nicer-fun *data*))))
This made me get it. It's very clear to me now how it works. I'm sure it will still take some time and effort to really grok this but now I'm confident I can do it.

Thanks to #lisp, Peter Seibel and Paul Gresham, I'm one step closer to 'lisp enlightenment' ;-)
I'm really appreciating the community for the help!

Who said people in #lisp weren't nice? Oh right that was me. Well I take it back!

0 comments: