Let's start Scheme

2019-01-02

(My) best practice of conditions

I've been writing portable/non-portable Scheme libraries for a rather long time and kind of getting my best practice of how to make conditions for libraries. So let me share the opinion.

Disclaimer

This is not community approved best practice nor it can be applied to the latest standard (R7RS). So I don't have any responsibility for your implementation can't handle or claims that nobody is writing this type of code :p

Basics

Condition system

R6RS has a very nice, (yet it was very controversial), the feature called condition system. It is built on top of, again very controversial, record type system. The basic ideas are:
  1. Conditions are inheritable (the absolute base is &condition)
  2. Conditions are compoundable. (using condition procedure)
These 2 concepts make the condition system very beautiful compare with, say, Java's exception.

How to use

The very basic usage is like this:
(define-condition-type &foo &error
  make-foo-error foo-error?)
This defines a condition type of &foo. Then you can signal the condition with raise or raise-continuable procedure.

My practices

Currently, I'm using conditions with the following rules, which I think the best at this moment.
  1. Should create at least one library specific condition
    If you are creating a library named foo, then the library should have a specific condition such as &foo unless the library provides only utilities. (e.g. (srfi :1) provides only list utilities).
  2. Must not use the error procedure
    If you see the standard error, then it's a bad sign. The standard conditions are good for general purposes but not good for a library specific error signalling.
  3. Should split conditions per phases
    If a library has several phases, then the conditions should be split. For example, (text json jmespath) has compilation and evaluation phases. So the condition should be split into 2, one for compilation time, the other one for evaluation time.
  4. Must not put too many fields
    Conditions are records, thus users can put as many as fields onto it. A condition must contain minimum information or resources. If you need more information, then use &irritants
  5. Should use composite then inheritance
    Conditions are records (I'm saying it twice because it's important), means you can only inherit one base condition. However, sometimes you want to put meta information such as &i/o. In this case use the condition procedure instead of creating a new condition type which inherits &i/o.
    For example, suppose you have &foo condition, which inherits &error. Now your library should also signale &i/o when an I/O operation failed. Theoretically, you have the following 2 options:
    1. Composite &i/o instance
    2. Create a new condition type which inherits &i/o
    As long as your base condition is not a subtype of &i/o, then you should use option number 1. In this manner, library users can handle the error situation easier by just adding a guard clause with foo-error? (suppose your condition predicate of &foo is foo-error?). And users can still check I/O error with i/o-error?

Conclusion

I'm quite happy with the above rules whenever I use the libraries constructed with it (sort of dogfooding).