chickadee » hahn



Hahn is a mechanism for documenting Scheme in the source-code itself; similar to Doxygen, Javadoc or Roxygen.

As an example, let's take this naïve Fibonacci:

(define (fibonacci n)
  @("Computes the nth [[|Fibonacci]]."
    "This naïve algorithm runs in ''O(2^n)''; using e.g. memoization,
we could bring it down to ''O(n)''."
    (n "The nth number to calculate")
    (@to "integer")
     "Computing the 6th Fibonnaci number (starting from 0)"
     (fibonacci 6)))
  (case n
    ((0) 0)
    ((1) 1)
    (else (+ (fibonacci (- n 1))
             (fibonacci (- n 2))))))

It produces the following output:


(fibonacci n) → number procedure

Computes the nth Fibonacci number.

This naïve algorithm runs in O(2^n); using e.g. memoization, we could bring it down to O(n).

The nth number to calculate
(define (fibonacci n)
  (case n ((0) 0) ((1) 1) (else (+ (fibonacci (- n 1)) (fibonacci (- n 2))))))

Computing the 6th Fibonnaci number (starting from 0)

(fibonacci 6)
=> 8


To document definitions, insert a so-called document-expression ("docexpr") after the variable (or variable-formals) and before the body of the definition.

The docexpr is an ampersand-prefixed expression containing a description; and optionally a longer description, parameters, return values and examples.


Variables such as constants and parameters, for example, only require a description:

(define k @("The Boltzmann constant") 1.38e-23)

The general form for variables is therefore something like:

(define <variable> @(<description> [<longer description>]) <expression>)


Procedures, on the other hand, can provide parameters, return-values and examples; parameters are specified with key-value lists containing the parameter and a description of the parameter; whereas return-values and examples are key-value lists containing the special keys @to and @example respectively.

(define (add x y)
  @("Adds two numbers."
    (x "The augend")
    (y "The addend")
    (@to "number")
    (@example "Adding two imprecise binary numbers"
              (add #b1# #b1##)))
  (+ x y))

Notice that @example takes a description, too; the general form for procedures is therefore something like:

(define (<variable> <formals>)
    [<longer description>]
    [(<formal-0> <formal-description-0>) ...
     (<formal-n> <formal-description-n>)]
    [(@to <return-type>)]
    [(@example <example-description>
               <example-expression-0> ... <example-expression-n>)]))

Special tags


The @example tag is useful for providing examples of procedure-application.

(define (quadratic-diophantine z)
  @("Finds a solution to the quadratic Diophantine equation x^2 + y^2 = z^2, given z."
    "Returns two values, x and y."
    (z "The known side")
    (@to "number, number")
    (@example "An example from Arithmetica II.VIII"
              (quadratic-diophantine 16)))
  (let* ((m (random (inexact->exact (floor (sqrt z)))))
         (n (sqrt (- z (expt m 2)))))
    (let ((x (- (expt m 2) (expt n 2)))
          (y (* 2 m n)))
      (values x y))))

The @example-no-eval tag is also useful for providing examples of procedure-application; the difference is that hahn does not attempt to evaluate them when rendering the documentation.

@Example-no-eval is useful when the examples are incomplete or pathological.

(define (find-fermat-counterexample)
  @("Finds positive integers a, b, c and n > 2 for which a^n + b^n = c^n."
    (@example-no-eval "Warning: this should never terminate."
  ;; The testable range is pretty small.
  (let ((range 8))
    (until (let ((a (+ (random range) 1))
                 (b (+ (random range) 1))
                 (n (+ (random range) 3)))
             (integer? (expt (+ (expt a n) (expt b n)) (/ 1 n)))))))

The @internal tag signifies that the documentation for the given expression should be suppressed; it is useful for internal documentation.

(define cat-alive?
  @("Qubit representing whether or not our cat is alive"
    "{{Cat-alive?}} is internal so that observers are forced to use
the {{observe!}} procedure."

The @no-source tag turns off the source-code listing that accompanies documented expressions.

(define (vote! candidate)
  @("[[|Votes]] for your candidate!"
    "This black-box voting procedure is the trade-secret of Biedolb,
Inc.; the source-code has been suppressed."
    (candidate "The candidate for which to vote")
  (register-vote! 'president-mccain))

The @to tag is optional and specifies the return value of a procedure; in the absence of @to, the return value is considered to be unspecified.

This procedure, for instance, has an unspecified return type:

(define (entangle! register . qs)
  @("Entangles qubits in a register."
    (register "The register in which to entangle")
    (qs "The qubits to be entangled"))
  (for-each (lambda (q) (set-register! q register)) qs))

whereas this one returns a specific type:

(define (apply-gate gate . qs)
  @("Applies the quantum-gate to the qubits."
    (gate "The quantum gate to apply")
    (qs "The qubits on which to apply it")
    (@to "qubit"))
  (make-qubit (matrix-multiply (apply quantum-state qs) gate)))

Top-level directives

Some of the top-level directives deal with metadata already gleaned from the .meta file and are therefore redundant; others deal with presentation, and are useful for crafting introductions, &c.


Author is the author of the egg; it overrides (author "Egg Author") from the .meta file.

@(author "Diophantus of Alexandria")

Description describes the egg; it overrides (synopsis "Egg synopsis") from the .meta file.

@(description "To divide a given square number into two squares")

Egg is the name of the egg; it overrides the filename of the .meta file (i.e. "name" from name.meta).

@(egg "arithmetica")

Email is the author's email; it overrides (email "") from the .meta file.

@(email "")

Example is a stand-alone example, as opposed to the @example tag that accompanies procedures.

@(example "Riastradh once asked why this does what it does; no one had
a satisfactory answer."
  (let* ((yin ((lambda (y) (newline) y)
                       (call/cc call/cc)))
                 (yang ((lambda (y) (write-char #\*) y)
                        (call/cc call/cc))))
            (yin yang)))

Example-no-eval is a stand-alone, unevaluated example; as opposed to the @example-no-eval tag that accompanies procedures.

@(example-no-eval "This will never terminate; thanks, Eli!"
                  ((lambda (x) (x x)) (lambda (x) (x x))))

Heading designates a section of the documentation right below the level of title.

@(heading "Arithmeticorum Liber II")

Noop is an artifact required to separate presentation-based directives from source-code that they don't belong to.

For instance:

@(heading "Abstract")
@(text "This is the body of the abstract.")

(define phi
  @("The heading and text above do not belong to this variable.")
  (/ (+ 1 (sqrt 5)) 2))

Repo overrides (repo "") from the .meta file.

(TODO: Implement this.)


Source provides a stand-alone source-code listing.

@(text "Through an aggressive heuristic, we've managed to solve the
halting problem: let's assume that if it doesn't finish in 1 second,
it never will.")

  (define (terminate? thunk)
    (let ((thread (thread-start! (make-thread thunk))))
      (and (thread-join! thread 1 #f)
           (thread-terminate! thread)

Subheading designates a section of the documentation right below the level of heading.

@(subheading "Quaestio VIII")

Subsubheading designates a section of the documentation right below the level of subheading.

@(subsubheading "Observatio domini Petri de Fermat")

Text is used for free-form text and can be useful for abstracts and explanatory material.

@(text "I have discovered a truly marvellous proof of this, which this
margin is too narrow to contain.")

Title overrides the egg-name as the title of the document.

@(title "Arithmetica")

Username is the username of the author on Chicken's wiki; it overrides (user "chicken-user") from the .meta file.

@(username "pfermat")

(TODO: Let's rename this user.)

Complete example

To tie everything together, here's a complete example; see the resulting documentation.

The .meta file

Hahn reads the metadata from the .meta file such as: synopsis, author, email, user, repo, depends.

((synopsis "Use the Landauer limit to calculate my program's entropy.")
 (author "Peter Danenberg")
 (email "")
 (user "klutometis")
 (repo "")
 (category math)
 (license "BSD")
 (depends hahn)
 (test-depends test)

The module file

The module file is a suitable place for putting introductory material about the egg; such as background information, abstract, &c.

It is also suitable for a high-level overview of what the module does.

@(heading "Landauer's principle")

@(text "[[|Landauer's
principle]] states that every irreversible operation produces
entropy; erasing one bit, for instance, generates at least ''kT'' ln
2 J of heat.")

@(text "We can use Landauer's principle to calculate a lower-bound on
the energy released by our program, given some number of

@(heading "Documentation")

(module landauer
  @("The Landauer module contains contains some constants, parameters
and procedures for calculating a lower-bound on the heat-dissipation
of programs.")
  (import chicken scheme)
  (include "landauer-core.scm"))

The source file

The source file contains the documentation of individual constants, parameters, records, procedures.

(define k @("The Boltzmann constant") 1.38e-23)

(define room-temperature @("Room temperature in K")
  (make-parameter 298.15))

(define (heat operations)
  @("Calculate a lower-bound on the heat dissipated by some number
of irreversible bit-operations."
    "Room-temperature is governed by the [[#room-temperature]]
    (operations "The number of irreversible bit-operations")
    (@to "number"))
  (* operations k (room-temperature) (log 2)))

The .setup file

The .setup file does two things:

  1. compiles each extension with -X hahn; and
  2. generates documention.

Extensions should be compiled with -X hahn; this strips the documentation from the source before compilation so that the compiler is not confused.

The hahn binary from hahn-utils generates the actual documentation; the hahn egg provides a convenience macro run-hahn so that installation does not fail for users who haven't installed hahn-utils.

There is a soft-dependency on the otherwise dependency-heavy egg hahn-utils: users don't have to have it unless they want to generate docs themselves, for some reason.

(use hahn setup-helper-mod)

 (extension-version "0.0.1")
 compile-options: '(-X hahn))

(run-hahn -o landauer.scm landauer-core.scm)


Contents »