Next: , Up: makers   [Index]


1.8.1 Introduction to maker macros

It happens to define a function or macro accepting a number of arguments greater than 3:

(define (the-func a b c d e f g h i)
  ...)

in these cases it can be difficult to remember the order of the arguments; it can also be desirable to define default values for some or all of the arguments, so that at the call site some arguments can be omitted in the source code. The syntax define-maker implements a solution to this problem.

The following examples show the expansion of a simple maker with neither fixed nor variable arguments, only optional clauses arguments:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker doit
  list ((alpha:     1)
        (beta:      2)
        (gamma:     3)))

(doit)                  → (list 1 2 3)

(doit (alpha:   10))    → (list 10 2 3)
(doit (beta:    20))    → (list 1 20 3)
(doit (gamma:   30))    → (list 1 2 30)

(doit (alpha:   10)
      (beta:    20))    → (list 10 20 3)

(doit (alpha:   10)
      (gamma:   30))    → (list 10 2 30)

(doit (gamma:   30)
      (beta:    20))    → (list 1 20 30)

(doit (alpha:   10)
      (beta:    20)
      (gamma:   30))    → (list 10 20 30)

(let ((b 7))
  (doit (beta:  (+ 6 (* 2 b)))
        (alpha: (+ 2 8)))
  → (list (+ 2 8)
            (+ 6 (* 2 b))
            3)
  #f)

(doit (alpha: 10 20 30))
→ (list 1 (list 10 20 30) 3)

notice the last example: when multiple values are used in an argument’s clause, they are enclosed in a list form; the following examples show the expansion of a maker with both fixed and variable arguments, plus optional clauses arguments:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker (doit a b)
    (list #\a #\b)
  ((alpha:  1)
   (beta:   2)
   (gamma:  3)))


(doit #\p #\q)          → (list #\a #\b #\p #\q 1 2 3)

(doit #\p #\q
      (alpha: 10))      → (list #\a #\b #\p #\q 10 2 3)

(doit #\p #\q
      (beta: 20))       → (list #\a #\b #\p #\q 1 20 3)

(doit #\p #\q
      (gamma: 30))      → (list #\a #\b #\p #\q 1 2 30)

(doit #\p #\q
      (alpha: 10)
      (beta:  20))      → (list #\a #\b #\p #\q 10 20 3)

(doit #\p #\q
      (alpha: 10)
      (gamma: 30))      → (list #\a #\b #\p #\q 10 2 30)

(doit #\p #\q
      (gamma: 30)
      (beta:  20))      → (list #\a #\b #\p #\q 1 20 30)

Each default value can be any expression and it is evaluated every time the maker is used:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define g
  (let ((counter 0))
    (lambda ()
      (set! counter (+ 1 counter))
      counter)))

(define default2 (- (/ 9 3) 1))

(define-maker doit
  list ((alpha:     1)
        (beta:      default2)
        (gamma:     (g))))

(doit)                  ⇒ ( 1  2  1)
(doit (alpha: 10))      ⇒ (10  2  2)
(doit (beta:  20))      ⇒ ( 1 20  3)
(doit (gamma: 30))      ⇒ ( 1  2 30)

A maker invocation can expand itself into another macro use; this allows us to detect whether an optional argument was used or not:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker doit
    subdoit
  ((alpha:  1)
   (beta:   2)
   (gamma:  sentinel)))

(define-syntax subdoit
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?alpha ?beta ?gamma)
       (and (identifier? #'?gamma)
            (free-identifier=? #'?gamma #'sentinel))
       #'(list ?alpha ?beta 3))
      ((_ ?alpha ?beta ?gamma)
       #'(list ?alpha ?beta ?gamma))
      )))

(doit)                  ⇒ ( 1  2  3)
(doit (alpha: 10))      ⇒ (10  2  3)
(doit (beta:  20))      ⇒ ( 1 20  3)
(doit (gamma: 30))      ⇒ ( 1  2 30)

and also to “unpack” multiple values used in the same argument clause:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker doit
  subdoit ((alpha:  1)
           (beta:   2)
           (gamma:  3)))

(define-syntax subdoit
  (lambda (stx)
    (syntax-case stx (list)
      ((_ ?alpha (list ?beta0 ...) ?gamma)
       #'(list ?alpha ?beta0 ... ?gamma))
      ((_ ?alpha ?beta ?gamma)
       #'(list ?alpha ?beta ?gamma))
      )))

(doit (alpha: 10)
       (beta: #\a #\b #\c)
       (gamma: 30))
⇒ (10 #\a #\b #\c 30)

We can define a maker in the body of a library and export it; we just have to remember to export the auxiliary syntaxes, too:

(library (the-lib)
  (export doit alpha beta gamma)
  (import (vicare)
    (prefix (vicare language-extensions makers) mk.))
  (define-auxiliary-syntaxes alpha beta gamma)
  (define-maker doit
    list ((alpha     1)
          (beta      2)
          (gamma     3))))

(library (exec)
  (export)
  (import (rnrs)
    (prefix (the-lib) lib.))
  (lib.doit)                  → (list 1 2 3)
  (lib.doit (lib.alpha   10)) → (list 10 2 3)
  (lib.doit (lib.beta    20)) → (list 1 20 3)
  (lib.doit (lib.gamma   30)) → (list 1 2 30)
  )

A number of options can be specified to customise the parsing of clauses. We can specify if a clause is mandatory or optional:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker doit
  list ((alpha:     1 (mk.mandatory))
        (beta:      2 (mk.optional))
        (gamma:     3)))

(doit (alpha: 10))              → (list 10 2 3)
(doit (beta:  20))              error→ missing clause "alpha:"

the default is for all the clauses to be optional. We can specify that a clause must be used along one or more other clauses:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntaxes alpha: beta: gamma:)

(define-maker doit
  list ((alpha:     1 (mk.with beta: gamma:))
        (beta:      2)
        (gamma:     3)))

(doit (alpha: 10)
      (beta:  20)
      (gamma: 30))              → (list 10 20 30)

(doit (beta: 20))               → (list 1 20 3)

(doit (alpha: 10)
      (gamma: 30))              error→ missing clause "beta:"

or we can specify that a clause must not be used along one or more other clauses:

(import (vicare)
  (prefix (vicare language-extensions makers) mk.))

(define-auxiliary-syntax alpha: beta: gamma:)

(define-maker doit
  list ((alpha:     1 (mk.without beta: gamma:))
        (beta:      2)
        (gamma:     3)))

(doit (alpha: 10)
      (beta:  20)
      (gamma: 30))              error→ invalid clauses mix

(doit (beta: 20))               → (list 1 20 3)

Next: , Up: makers   [Index]