t.link Software Links

Scheme with Type Annotations

Type annotations
Type definitions
Run-time checks
let/a and let/a*
Help information
Bug, Design Flaw, or Feature?


This Scheme package implements some macros: set/a!, define/a, lambda/a, let/a, and let/a*. These macros aim at dealing with optional arguments, type annotations, and interactive retrieval of help information in Scheme.

Type annotations

Type annotations have the form: NAME:TYPE. The name may contain colons, provided that there is a type annotation or that the last character is a colon, which will not be part of the name. If there is no type annotation, a variable is of type value. Thus, NAME:value is the same as NAME.


Type definitions

A type definition is a list of tags and corresponding values. New types are being defined with the command (tl-type-set! NAME '(TAG VALUE ...)). Here are some examples:

(tl-type-set! integer0-10
         `(&type integer
           &range ,(lambda (val) (and (>= val 0) (<= val 10)))))

(tl-type-set! my-number '(&or (integer real)))

(tl-type-set! colour '(&enum (red blue yellow black white)))
(tl-type-set! colours '(&list colour))

(tl-type-set! lower-case `(&check ,char-lower-case?))
(tl-type-set! lower-string '(&string lower-case))

(tl-type-set! char-numeric `(&check ,char-numeric?))
(tl-type-set! lower-case-or-number '(&or (lower-case char-numeric)))
(tl-type-set! lower-string '(&string lower-case-or-number))

(tl-type-set! small-integer `(&type integer &min 0 &max 10))
(tl-type-set! small-even-integer `(&type small-integer &min 2 &step 2))

As you can see from these examples, there are some predefined tags with special meanings:

&type SUPER
The value is (also) of type SUPER.
&or (TYPE1 TYPE2 ...)
The value is of either type.
&check FN/1
A function which takes one argument and returns #t if a value conforms with the type definition. This one should only be used for "native" types and class conformance.
&enum (SYMBOL1 SYMBOL2 ...)
The value is one of these symbols.
&list, &vector, &string TYPE
The value is a "collection" the elements of which are of type TYPE.
&min, &max, &step NN
Define ranges. The &step tag can only be used with integers.
For internal use only. Don't use this tag.

Run-time checks

This type system was made to allow run-time checks and to emulate some kind of assertion mechanism as it is known from languages like Eiffel. You can switch on and off certain checks when debugging your program. Which run-time checks are performed is controlled by the variables tl-types-excluded, tl-types-included, tl-types-enabled?, and the following functions:

Disable run-time checks.
Enable run-time checks.
(tl-type-exclude . types)
Don't perform run-time checks for these types even if assertion checking is enabled.
(tl-type-include . types)
Perform run-time checks for these types even if assertion checking is disabled.

let/a and let/a*


(let/a* ((a:integer 1) (b:integer a))
  (+ a b))



(set/a! x:integer 2)
(+ x 2)



(define x (lambda/a (a:integer b:integer) (+ a b)))


This form (but also lambda/a) allows the programmer to annotate his/her definitions with some additional information. These tags are:

&optional (NAME DEFAULT)
Optional arguments
&rest NAME
Remaining arguments
&returns TYPE or (TYPE1 TYPE2 ...)
The function's return value(s)

As you can see from the example below, this approach is inspired by Lisp's defun.

Example definition & function calls:

(define/a (test a b
                 &optional (c:integer 0)
                 &rest d
                 &returns (integer integer integer))
  (values (+ a b) (+ a c) (apply + `(,a ,b ,c . ,d))))
(test 1 2)-> {3 1 3}
(test 1 2 (&optional 'c 3))-> {3 4 6}
(test 1 2 4 5 6)-> {3 1 18}
(test 1 2 (&optional 'c 3) 4 5 6)-> {3 4 21}
(test 1 2 (&optional 'c 3.5))-> type error
(test 1.5 2 (&optional 'c 3))-> {3.5 4 6.5} -> type error

Anyway, you can also use the Scheme way to define a rest-argument.

(define/a (test a b c . d)
  (values (+ a b) (+ a c) (apply + `(,a ,b ,c . ,d))))

(define/a (test a b c
                 &returns (integer integer integer)
                 . d)
  (values (+ a b) (+ a c) (apply + `(,a ,b ,c . ,d))))

Help information

It is possible to add a documentations string to the define/a form. You don't have to use this feature if you don't like it.


(define/a (test a b)
  "Surprise, it's a test"
  (+ a b))

These document strings are being gathered in a central database. The information can be retrieved interactively by using the command tl-info, e.g. (tl-info 'test). In a future version it will also be possible to automatically create a html documention.

Bug, Design Flaw, or Feature?

When using lambda/a or define/a two variables are being defined: &optional-args (optional arguments) and &args (optional and "rest" arguments). This means you can't access similarly named variables "outside" the function. On the other hand, this, eh, feature can be used to easily pass on optional arguments to other functions.


The code has been tested (more or less) with MzScheme and should be fairly portable as only low level macros are used. Anyway, in order to use this package you will have to ...

  1. put the files into a subdirectory called "tsl" of where your collections reside
  2. type (require-library "tl-define.scm" "tsl").
  3. if you want to pass optional arguments to a function defined using define/a and if tl-define isn't already loaded, you will have to load (or require) "tl-optional.scm".

Preliminary version 0110a: define-a.tar.gz

(c) 2001 Thomas Link (last updated Mar 27 2003) home top