|
This chapter describes the main forms available in STklos. R5RS
constructions are given
very succinctly here for reference. See [12] for a complete
description.
2.1 Literal expressions
(quote <datum>) | R5RS syntax |
'<datum>
The quoting mechanism is identical to R5RS, except that keywords
constants evaluate "to themselves" as numerical constants, string
constants, character constants, and boolean constants
'"abc" ⇒ "abc"
"abc" ⇒ "abc"
'145932 ⇒ 145932
145932 ⇒ 145932
'#t ⇒ #t
#t ⇒ #t
:foo ⇒ :foo
':foo ⇒ :foo
|
Note: R5RS requires to quote constant lists and
constant vectors. This is not necessary with STklos. |
2.2 Procedures
(lambda <formals> <body>) | STklos syntax |
A lambda expression evaluates to a procedure. STklos lambda expression
have been extended to allow a optional and keyword parameters.
<formals> should have one of the following forms:
-
(<variable1> ...)
The procedure takes a fixed number of arguments;
when the procedure is called, the arguments will be stored in the
bindings of the corresponding variables. This form is identical to
R5RS.
-
<variable>
The procedure takes any number of arguments; when the
procedure is called, the sequence of actual arguments is converted into
a newly allocated list, and the list is stored in the binding of the
<variable> . This form is identical to R5RS.
-
(<variable1> ... <variablen> . <variablen+1>)
If a space-delimited
period precedes the last variable, then the procedure takes n or more
arguments, where n is the number of formal arguments before the period
(there must be at least one). The value stored in the binding of the
last variable will be a newly allocated list of the actual arguments left
over after all the other actual arguments have been matched up against
the other formal arguments. This form is identical to R5RS.
-
(<variable1 ... <variablen> [:optional ...] [:rest ...] [:key ...])
This form is specific to STklos and allows to have procedure with
optional and keyword parameters. The form :optional allows to specify
optional parameters. All the parameters specified after :optional to the end
of <formals> (or until a :rest or :key ) are optional parameters. An
optional parameter can declared as:
-
variable : if a value is passed when the procedure is called, it will be
stored in the binding of the corresponding variable, otherwise the value #f
will be stored in it.
-
(variable value) : if a value is passed when the procedure is called, it
will be stored in the binding of the corresponding variable, otherwise value
will be stored in it.
-
(variable value test?) : if a value is passed when the procedure is called, it
will be stored in the binding of the corresponding variable, otherwise value
will be stored in it. Furthermore, test? will be given the value #t if
a value is passed for the given variable, otherwise test? is set to #f
Hereafter are some examples using :optional parameters
((lambda (a b :optional c d) (list a b c d)) 1 2)
⇒ (1 2 #f #f)
((lambda (a b :optional c d) (list a b c d)) 1 2 3)
⇒ (1 2 3 #f)
((lambda (a b :optional c (d 100)) (list a b c d)) 1 2 3)
⇒ (1 2 3 100)
((lambda (a b :optional c (d #f d?)) (list a b c d d?)) 1 2 3)
⇒ (1 2 3 #f #f)
|
The form :rest parameter is similar to the dot notation seen before.
It is used before an identifier to collects the parameters in a single
binding:
((lambda (a :rest b) (list a b)) 1)
⇒ (1 ())
((lambda (a :rest b) (list a b)) 1 2)
⇒ (1 (2))
((lambda (a :rest b) (list a b)) 1 2 3)
⇒ (1 (2 3))
|
The form :key allows to use keyword parameter passing. All the parameters
specified after :key to the end of <formals> are keyword parameters. A
keyword parameter can be declared using the three forms given for optional
parameters. Here are some examples illustrating how to declare and how to use
keyword parameters:
((lambda (a :key b c) (list a b c)) 1 :c 2 :b 3)
⇒ (1 3 2)
((lambda (a :key b c) (list a b c)) 1 :c 2)
⇒ (1 #f 2)
((lambda (a :key (b 100 b?) c) (list a b c b?)) 1 :c 2)
⇒ (1 100 2 #f)
|
At last, here is an example showing :optional :rest and :key parameters
(define f (lambda (a :optional b :rest c :key d e)
(list a b c d e)))
(f 1) ⇒ (1 #f () #f #f)
(f 1 2) ⇒ (1 2 () #f #f)
(f 1 2 :d 3 :e 4) ⇒ (1 2 (:d 3 :e 4) 3 4)
(f 1 :d 3 :e 4) ⇒ (1 #f (:d 3 :e 4) 3 4)
|
|
(closure? obj) | STklos procedure |
Returns #t if obj is a procedure created with the lambda syntax and
#f otherwise. |
(case-lambda <clause> ...) | STklos syntax |
Each <clause> should have the form (<formals> <body>) , where
<formals> is a formal arguments list as for lambda .
Each <body> is a <tail-body> , as defined in R5RS.
A case-lambda expression evaluates to a procedure that
accepts a variable number of arguments and is lexically scoped in
the same manner as procedures resulting from lambda
expressions. When the procedure is called with some arguments
v1 ... vk , then the first <clause> for which the arguments agree
with <formals> is selected, where agreement is specified as for the
<formals> of a lambda expression. The variables of <formals>
are bound to fresh locations, the values v1 ... vk are stored in those
locations, the <body> is evaluated in the extended environment,
and the results of <body> are returned as the results of the
procedure call.
It is an error for the arguments not to agree with the <formals>
of any <clause> .
This form is defined in SRFI-16
(Syntax for procedures of variable arity).
(define plus
(case-lambda
(() 0)
((x) x)
((x y) (+ x y))
((x y z) (+ (+ x y) z))
(args (apply + args))))
(plus) ⇒ 0
(plus 1) ⇒ 1
(plus 1 2 3) ⇒ 6
((case-lambda
((a) a)
((a b) (* a b)))
1 2 3) ⇒ error
|
|
2.3 Assignments
(set! <variable> <expression>) | R5RS syntax |
(set! (<proc> <arg> ...) <expression>)
The first form of set! is the R5RS one:
<Expression> is evaluated, and the resulting value is stored in
the location to which <variable> is bound. <Variable> must be bound
either in some region enclosing the set! expression or at top level.
(define x 2)
(+ x 1) ⇒ 3
(set! x 4) ⇒ unspecified
(+ x 1) ⇒ 5
|
The second form of set! is defined in SRFI-17
(Generalized set!):
This special form set!
is extended so the first operand can be a procedure application, and not
just a variable. The procedure is typically one that extracts a component
from some data structure. Informally, when the procedure is called in the
first operand of set! , it causes the corresponding component to be
replaced by the second operand. For example,
would be equivalent to:
Each procedure that may be used as the first operand to set! must have
a corresponding setter procedure. The procedure setter (see below)
takes a procedure and returns the corresponding setter procedure.
So,
(set! (proc arg ...) value)
|
is equivalent to the call
((setter proc) arg ... value)
|
The result of the set! expression is unspecified. |
(setter proc) | R5RS procedure |
Returns the setter associated to a proc . Setters are defined in the
SRFI-17
(Generalized set!) document. A setter proc, can be used in a generalized
assignment, as described in set! .
To associate s to the procedure p , use the following form:
For instance, we can write
(set! (setter car) set-car!)
|
The following standard procedures have pre-defined setters:
(set! (car x) v) == (set-car! x v)
(set! (cdr x) v) == (set-cdr! x v)
(set! (string-ref x i) v) == (string-set! x i v)
(set! (vector-ref x i) v) == (vector-set! x i v)!
(set! (slot-ref x 'name) v) == (slot-set! x 'name v)
(set! (struct-ref x 'name) v) == (struct-set! x 'name v)
|
Furhermore, parameters objects
are their own setter:
(real-precision) ⇒ 15
(set! (real-precision) 12)
(real-precision) ⇒ 12
|
|
2.4 Conditionals
(if <test> <consequent> <alternate>) | R5RS syntax |
(if <test> <consequent>)
An if expression is evaluated as follows: first, <test> is
evaluated. If it yields a true value, then <consequent> is
evaluated and its value(s) is(are) returned. Otherwise <alternate>
is evaluated and its value(s) is(are) returned. If <test> yields a
false value and no <alternate> is specified, then the result of the
expression is void.
(if (> 3 2) 'yes 'no) ⇒ yes
(if (> 2 3) 'yes 'no) ⇒ no
(if (> 3 2)
(- 3 2)
(+ 3 2)) ⇒ 1
|
|
(cond <clause1> <clause2> ...) | R5RS syntax |
In a cond , each <clause> should be of the form
(<test> <expression1> ...)
|
where <test> is any expression. Alternatively, a <clause> may be
of the form
The last <clause> may be an "else clause," which has the form
(else <expression1> <expression2> ...)
|
A cond expression is evaluated by evaluating the <test> expressions
of successive <clause> s in order until one of them evaluates to a
true value When a <test> evaluates to a true value, then the
remaining <expression> s in its <clause> are evaluated in order,
and the result(s) of the last <expression> in the <clause> is(are)
returned as the result(s) of the entire cond expression. If the
selected <clause> contains only the <test> and no <expression> s,
then the value of the <test> is returned as the result. If the
selected <clause> uses the ⇒ alternate form, then the
<expression> is evaluated. Its value must be a procedure that
accepts one argument; this procedure is then called on the value of
the <test> and the value(s) returned by this procedure is(are)
returned by the cond expression.
If all <test> s evaluate to false
values, and there is no else clause, then the result of the
conditional expression is void; if there is an else clause,
then its <expression> s are evaluated, and the value(s) of the last
one is(are) returned.
(cond ((> 3 2) 'greater)
((< 3 2) 'less)) ⇒ greater
(cond ((> 3 3) 'greater)
((< 3 3) 'less)
(else 'equal)) ⇒ equal
(cond ((assv 'b '((a 1) (b 2))) ⇒ cadr)
(else #f)) ⇒ 2
|
|
(case <key> <clause1> <clause2> ...) | R7RS syntax |
In a case , each <clause> should have the form
((<datum1> ...) <expression1> <expression2> ...),
|
where each <datum> is an external representation of some object. All the
<datum> s must be distinct. The last <clause> may be an "else clause," which
has the form
(else <expression1> <expression2> ...).
|
A case expression is evaluated as follows. <Key> is evaluated and
its result is compared against each <datum> . If the result of
evaluating <key> is equivalent (in the sense of eqv?) to a
<datum> , then the expressions in the corresponding <clause> are
evaluated from left to right and the result(s) of the last expression
in the <clause> is(are) returned as the result(s) of the case
expression. If the result of evaluating <key> is different from
every <datum> , then if there is an else clause its expressions are
evaluated and the result(s) of the last is(are) the result(s) of the
case expression; otherwise the result of the case expression is void.
If the selected <clause> or else clause uses the ⇒ alternate
form, then the expression is evaluated. It is an error if
its value is not a procedure accepting one argument. This
procedure is then called on the value of the hkeyi and the
values returned by this procedure are returned by the case
expression.
(case (* 2 3)
((2 3 5 7) 'prime)
((1 4 6 8 9) 'composite)) ⇒ composite
(case (car '(c d))
((a) 'a)
((b) 'b)) ⇒ void
(case (car '(c d))
((a e i o u) 'vowel)
((w y) 'semivowel)
(else 'consonant)) ⇒ consonant
(case (car '(c d))
((a e i o u) 'vowel)
((w y) 'semivowel)
(else ⇒ (lambda (x) (x)))) ⇒ c
|
|
(and <test1> ...) | R5RS syntax |
The <test> expressions are evaluated from left to right, and the
value of the first expression that evaluates to a false value is
returned. Any remaining expressions are not evaluated. If all the
expressions evaluate to true values, the value of the last expression
is returned. If there are no expressions then #t is returned.
(and (= 2 2) (> 2 1)) ⇒ #t
(and (= 2 2) (< 2 1)) ⇒ #f
(and 1 2 'c '(f g)) ⇒ (f g)
(and) ⇒ #t
|
|
(or <test1> ...) | R5RS syntax |
The <test> expressions are evaluated from left to right, and the
value of the first expression that evaluates to a true value is
returned. Any remaining expressions are not evaluated. If all
expressions evaluate to false values, the value of the last expression
is returned. If there are no expressions then #f is returned.
(or (= 2 2) (> 2 1)) ⇒ #t
(or (= 2 2) (< 2 1)) ⇒ #t
(or #f #f #f) ⇒ #f
(or (memq 'b '(a b c))
(/ 3 0)) ⇒ (b c)
|
|
(when <test> <expression1> <expression2> ...) | STklos syntax |
If the <test> expression yields a true value, the <expression> s are
evaluated from left to right and the value of the last <expression> is
returned. Otherwise, when returns void. |
(unless <test> <expression1> <expression2> ...) | STklos syntax |
If the <test> expression yields a false value, the <expression> s are
evaluated from left to right and the value of the last <expression> is
returned. Otherwise, unless returns void. |
2.5 Binding Constructs
The three binding constructs let , let* , and
letrec are available in STklos.
These constructs differ in the regions they establish for
their variable bindings. In a let expression, the initial values are
computed before any of the variables become bound; in a let*
expression, the bindings and evaluations are performed sequentially; while in a
letrec expression, all the bindings are in effect while their initial
values are being computed, thus allowing mutually recursive definitions.
STklos also provides a fluid-let form which is described below.
(let <bindings> <body>) | R5RS syntax |
(let <variable> <bindings> <body>)
In a let , <bindings> should have the form
((<variable1> <init1>) ...)
|
where each <init> is an expression, and <body> should be a sequence of one or
more expressions. It is an error for a <variable> to appear more than once in
the list of variables being bound.
The <init> s are evaluated in the current environment (in some
unspecified order), the <variable> s are bound to fresh locations holding the
results, the <body> is evaluated in the extended environment, and the value(s)
of the last expression of <body> is(are) returned. Each binding of a
<variable> has <body> as its region.
(let ((x 2) (y 3))
(* x y)) ⇒ 6
(let ((x 2) (y 3))
(let ((x 7)
(z (+ x y)))
(* z x))) ⇒ 35
|
The second form of let , which is generally called a named let,
is a variant on the syntax of let which provides a more general
looping construct than do (@pxref{do}) and may also be used to
express recursions. It has the same syntax and semantics as ordinary
let except that <variable> is bound within <body> to a procedure whose
formal arguments are the bound variables and whose body is <body> .
Thus the execution of <body> may be repeated by invoking the procedure
named by <variable> .
(let loop ((numbers '(3 -2 1 6 -5))
(nonneg '())
(neg '()))
(cond ((null? numbers) (list nonneg neg))
((>= (car numbers) 0)
(loop (cdr numbers)
(cons (car numbers) nonneg)
neg))
((< (car numbers) 0)
(loop (cdr numbers)
nonneg
(cons (car numbers) neg)))))
⇒ ((6 1 3) (-5 -2))
|
|
(let* <bindings> <body>) | R5RS syntax |
In a let* , <bindings> should have the same form as in a let (however, a
<variable> can appear more than once in the list of variables being bound).
Let* is similar to let , but the bindings are performed sequentially
from left to right, and the region of a binding indicated by
is that part of the let* expression to the right of the binding. Thus
the second binding is done in an environment in which the first binding is
visible, and so on.
(let ((x 2) (y 3))
(let* ((x 7)
(z (+ x y)))
(* z x))) ⇒ 70
|
|
(letrec <bindings> <body>) | R5RS syntax |
<bindings> should have the form as in let .
The <variable> s are bound to fresh locations holding undefined
values, the <init> s are evaluated in the resulting environment (in
some unspecified order), each <variable> is assigned to the result
of the corresponding <init> , the <body> is evaluated in the
resulting environment, and the value(s) of the last expression in
<body> is(are) returned. Each binding of a <variable> has the
entire letrec expression as its region, making it possible to define
mutually recursive procedures.
(letrec ((even? (lambda (n)
(if (zero? n)
#t
(odd? (- n 1)))))
(odd? (lambda (n)
(if (zero? n)
#f
(even? (- n 1))))))
(even? 88))
⇒ #t
|
|
(letrec* <bindings> <body>) | R7RS syntax |
<bindings> should have the form as in let and body is a sequence
of zero or more definitions followed by one or more expressions.
The <variable> s are bound to fresh locations, each variable is
assigned in left-to-right order to the result of evaluating the
corresponding init , the body is evaluated in the resulting
environment, and the values of the last expression in body are
returned. Despite the left-to-right evaluation and assignment order,
each binding of a variable has the entire letrec* expression as its
region, making it possible to define mutually recursive procedures.
If it is not possible to evaluate each init without assigning or
referring to the value of the corresponding variable or the
variable of any of the bindings that follow it in bindings , it is
an error.
(letrec* ((p (lambda (x)
(+ 1 (q (- x 1)))))
(q(lambda (y)
(if (zero? y)
0
(+ 1 (p (- y 1))))))
(x (p 5))
(y x))
y) ⇒ 5
|
|
(let-values ((<formals> <expression>) ...) <body>) | R7RS syntax |
Each <formals> should be a formal arguments list as for a lambda expression.
The <expression> s are evaluated in the current environment,
the variables of the <formals> are bound to fresh locations, the return
values of the <expression> s are stored in the variables, the <body> is
evaluated in the extended environment, and the values of the last expression
of <body> are returned.
The matching of each <formals> to values is as for the matching of
<formals> to arguments in a lambda expression, and it is an error
for an <expression> to return a number of values that does not match
its corresponding <formals> .
(let-values (((root rem) (exact-integer-sqrt 32)))
(* root rem)) ⇒ 35
(let ((a 'a) (b 'b) (x 'x) (y 'y))
(let-values (((a b) (values x y))
((x y) (values a b)))
(list a b x y))) ⇒ (x y a b)
|
|
(let-values ((<formals> <expression>) ...) <body>) | R7RS syntax |
Each <formals> should be a formal arguments list as for a lambda expression.
let*-values is similar to let-values , but the bindings are performed
sequentially from left to right, and the region of a binding indicated by
(<formals> <expression>) is that part of the let*-values expression to
the right of the binding. Thus the second binding is done in an environment
in which the first binding is visible, and so on.
(let ((a 'a) (b 'b) (x 'x) (y 'y))
(let*-values (((a b) (values x y))
((x y) (values a b)))
(list a b x y))) ⇒ (x y x y)
|
|
(fluid-let <bindings> <body>) | STklos syntax |
The <bindings> are evaluated in the current environment, in some
unspecified order, the current values of the variables present in
<bindings> are saved, and the new evaluated values are assigned to the
<bindings> variables. Once this is done, the expressions of <body>
are evaluated sequentially in the current environment; the value of the
last expression is the result of fluid-let . Upon exit, the stored
variables values are restored. An error is signalled if any of the
<bindings> variable is unbound.
(let* ((a 'out)
(f (lambda () a)))
(list (f)
(fluid-let ((a 'in)) (f))
(f))) ⇒ (out in out)
|
When the body of a fluid-let is exited by invoking a continuation,
the new variable values are saved, and the variables are set to their old
values. Then, if the body is reentered by invoking a continuation, the old
values are saved and new values are restored. The following example illustrates
this behavior
(let ((cont #f)
(l '())
(a 'out))
(set! l (cons a l))
(fluid-let ((a 'in))
(set! cont (call-with-current-continuation (lambda (k) k)))
(set! l (cons a l)))
(set! l (cons a l))
(if cont (cont #f) l)) ⇒ (out in out in out)
|
|
2.6 Sequencing
(begin <expression1> <expression2> ...) | R5RS syntax |
The <expression> s are evaluated sequentially from left to right, and the
value(s) of the last <expression> is(are) returned. This expression type is
used to sequence side effects such as input and output.
(define x 0)
(begin (set! x 5)
(+ x 1)) ⇒ 6
(begin (display "4 plus 1 equals ")
(display (+ 4 1))) -| 4 plus 1 equals 5
⇒ void
|
|
2.7 Iterations
(do [[<var1> <init1> <step1>] ...] [<test> <expr> ...] <command> ...) | R5RS syntax |
Do is an iteration construct. It specifies a set of variables to be
bound, how they are to be initialized at the start, and how they are
to be updated on each iteration. When a termination condition is met,
the loop exits after evaluating the <expr> s.
Do expressions are evaluated as follows: The <init> expressions
are evaluated (in some unspecified order), the <var> s are bound
to fresh locations, the results of the <init> expressions are stored
in the bindings of the <var> s, and then the iteration phase
begins.
Each iteration begins by evaluating <test> ; if the result is false
then the <command> expressions are evaluated in order for effect,
the <step> expressions are evaluated in some unspecified order, the
<var> s are bound to fresh locations, the results of the <step> s
are stored in the bindings of the <var> s, and the next iteration
begins.
If <test> evaluates to a true value, then the <expr> s are
evaluated from left to right and the value(s) of the last <expr>
is(are) returned. If no <expr> s are present, then the value of
the do expression is void.
The region of the binding of a <var> consists of the entire do
expression except for the <init> s. It is an error for a <var> to
appear more than once in the list of do variables.
A <step> may be omitted, in which case the effect is the same as if
had been written.
(do ((vec (make-vector 5))
(i 0 (+ i 1)))
((= i 5) vec)
(vector-set! vec i i)) ⇒ #(0 1 2 3 4)
(let ((x '(1 3 5 7 9)))
(do ((x x (cdr x))
(sum 0 (+ sum (car x))))
((null? x) sum))) ⇒ 25
|
|
(dotimes [var count] <expression1> <expression2> ... ) | STklos syntax |
(dotimes [var count result] <expression1> <expression2> ... )
Evaluates the count expression, which must return an
integer and then evaluates the <expression> s once for each
integer from zero (inclusive) to count (exclusive), in order,
with the symbol var bound to the integer; if the value of
count is zero or negative, then the <expression> s are not
evaluated. When the loop completes, result is evaluated and its
value is returned as the value of the dotimes construction. If
result is omitted, dotimes result is void.
(let ((l '()))
(dotimes (i 4 l)
(set! l (cons i l)))) ⇒ (3 2 1 0)
|
|
(while <test> <expression1> <expression2> ...) | STklos syntax |
While evaluates the <expression> s until <test> returns a false
value. The value returned by this form is void. |
(until <test> <expression1> <expression2> ...) | STklos syntax |
Until evaluates the <expression> s until <while> returns a false
value. The value returned by this form is void. |
2.8 Delayed Evaluation
(delay <expression>) | R5RS syntax |
The delay construct is used together with the procedure force
to implement lazy evaluation or call by need. (delay
<expression>) returns an object called a promise which at some
point in the future may be asked (by the force procedure) to
evaluate <expression> , and deliver the resulting value.
The effect of <expression> returning multiple values is unpredictable.
See the description of force (@pxref{force}) for a more complete
description of delay . |
(delay-force <expression>) | R7RS syntax |
The expression (delay-force expression) is conceptually similar
to (delay (force expression)) , with the difference that forcing the result
of delay-force will in effect result in a tail call to (force expression) ,
while forcing the result of (delay (force expression)) might not. Thus
iterative lazy algorithms that might result in a long series of chains of
delay and force can be rewritten using delay-force to prevent consuming
unbounded space during evaluation. |
(force promise) | R5RS procedure |
Forces the value of promise (see delay). If no value has been
computed for the promise, then a value is computed and
returned. The value of the promise is cached (or "memoized") so
that if it is forced a second time, the previously computed value
is returned.
(force (delay (+ 1 2))) ⇒ 3
(let ((p (delay (+ 1 2))))
(list (force p) (force p))) ⇒ (3 3)
(define a-stream
(letrec ((next (lambda (n)
(cons n (delay (next (+ n 1)))))))
(next 0)))
(define head car)
(define tail (lambda (stream) (force (cdr stream))))
(head (tail (tail a-stream))) ⇒ 2
|
Force and delay are mainly intended for programs written in
functional style. The following examples should not be considered
to illustrate good programming style, but they illustrate the
property that only one value is computed for a promise, no matter
how many times it is forced.
(define count 0)
(define p (delay (begin (set! count (+ count 1))
(if (> count x)
count
(force p)))))
(define x 5)
p ⇒ a promise
(force p) ⇒ 6
p ⇒ a promise, still
(begin (set! x 10)
(force p)) ⇒ 6
|
Note: See R5RS for details on a posssible way to implement
force and delay . |
(promise? obj) | R7RS procedure |
Returns #t if obj is a promise, otherwise returns #f . |
(make-promise obj) | R7RS procedure |
The make-promise procedure returns a promise which,
when forced, will return obj . It is similar to delay , but
does not delay its argument: it is a procedure rather than
syntax. If obj is already a promise, it is returned. |
2.9 Quasiquotation
(quasiquote <template>) | R5RS syntax |
`<template>
"Backquote" or "quasiquote" expressions are useful for constructing a
list or vector structure when most but not all of the desired structure
is known in advance. If no commas appear within the <template> ,
the result of evaluating `<template> is equivalent to the result of
evaluating '<template> . If a comma appears within the
<template> , however, the expression following the comma is evaluated
("unquoted") and its result is inserted into the structure instead of
the comma and the expression. If a comma appears followed immediately
by an at-sign (@), then the following expression must evaluate to a
list; the opening and closing parentheses of the list are then
"stripped away" and the elements of the list are inserted in place of the comma
at-sign expression sequence. A comma at-sign should only appear within
a list or vector <template> .
`(list ,(+ 1 2) 4) ⇒ (list 3 4)
(let ((name 'a)) `(list ,name ',name))
⇒ (list a (quote a))
`(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b)
⇒ (a 3 4 5 6 b)
`((foo ,(- 10 3)) ,@(cdr '(c)) . )
⇒ ((foo 7) . cons)
`#(10 5 ,(sqrt 4) ,@(map sqrt '(16 9)) 8)
⇒ #(10 5 2 4 3 8)
|
Quasiquote forms may be nested. Substitutions are made only for unquoted
components appearing at the same nesting level as the outermost backquote.
The nesting level increases by one inside each successive quasiquotation,
and decreases by one inside each unquotation.
`(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f)
⇒ (a `(b ,(+ 1 2) ,(foo 4 d) e) f)
(let ((name1 'x)
(name2 'y))
`(a `(b ,,name1 ,',name2 d) e))
⇒ (a `(b ,x ,'y d) e)
|
The two notations `<template> and (quasiquote <template>) are identical
in all respects. ,<expression> is identical to (unquote <expression>) , and
,@<expression> is identical to (unquote-splicing <expression>) .
|
2.10 Macros
STklos supports hygienic macros such as the ones defined in R5RS
as well as low level macros. Low level macros are defined with define-macro whereas
R5RS macros are defined with define-syntax
1. Hygienic macros use the implementation
called Macro by Example (Eugene Kohlbecker, R4RS) done by Dorai
Sitaram. This implementation generates low level STklos
macros. This implementation of hygienic macros is not expensive. The major drawback of this implementation is that the macros are
not referentially transparent (see section `Macros' in
R4RS for details). Lexically scoped macros
(i.e., let-syntax and letrec-syntax are not
supported). In any case, the problem of referential transparency
gains poignancy only when let-syntax and
letrec-syntax are used. So you will not be courting
large-scale disaster unless you're using system-function names as
local variables with unintuitive bindings that the macro can't
use. However, if you must have the full R5RS macro
functionality, you can do
to have access to the more featureful (but also more expensive)
versions of syntax-rules . Requiring "full-syntax"
loads the version 2.1 of an implementation of hygienic macros by
Robert Hieb and R. Kent Dybvig.
(define-macro (<name> <formals>) <body>) | STklos syntax |
(define-macro <name> (lambda <formals> <body>))
define-macro can be used to define low-level macro
(i.e. non hygienic macros). This form is similar to the
defmacro form of Common Lisp.
(define-macro (incr x) `(set! ,x (+ ,x 1)))
(let ((a 1)) (incr a) a) ⇒ 2
(define-macro (when test . body)
`(if ,test ,@(if (null? (cdr body)) body `((begin ,@body)))))
(macro-expand '(when a b)) ⇒ (if a b)
(macro-expand '(when a b c d))
⇒ (if a (begin b c d))
(define-macro (my-and . exprs)
(cond
((null? exprs) #t)
((= (length exprs) 1) (car exprs))
(else `(if ,(car exprs)
(my-and ,@(cdr exprs))
#f))))
(macro-expand '(my-and a b c))
⇒ (if a (my-and b c) #f)
|
|
(define-syntax <identifier> <transformer-spec>) | R5RS syntax |
<Define-syntax> extends the top-level syntactic environment by binding
the <identifier> to the specified transformer.
Note: <transformer-spec> should be an instance of syntax-rules .
(define-syntax let*
(syntax-rules ()
((let* () body1 body2 ...)
(let () body1 body2 ...))
((let* ((name1 val1) (name2 val2) ...)
body1 body2 ...)
(let ((name1 val1))
(let* (( name2 val2) ...)
body1 body2 ...))))
|
|
(syntax-rules <literals> <syntax-rule> ...) | R5RS syntax |
<literals> is a list of identifiers, and each <syntax-rule> should be of
the form
An instance of <syntax-rules> produces a new macro transformer by
specifying a sequence of hygienic rewrite rules. A use of a macro
whose name is associated with a transformer specified by
<syntax-rules> is matched against the patterns contained in the
<syntax-rules>, beginning with the leftmost syntax-rule. When a match is
found, the macro use is transcribed hygienically according to the
template.
Each pattern begins with the name for the macro. This name is not
involved in the matching and is not considered a pattern variable or
literal identifier.
Note: For a complete description of the Scheme pattern language,
refer to R5RS. |
(let-syntax <bindings> <body>) | R5RS syntax |
<Bindings> should have the form
((<keyword> <transformer spec>) ...)
|
Each <keyword> is an identifier, each <transformer spec> is an instance of
syntax-rules , and <body> should be a sequence of one or more expressions. It
is an error for a <keyword> to appear more than once in the list of keywords
being bound.
The <body> is expanded in the syntactic environment obtained by
extending the syntactic environment of the let-syntax expression with macros
whose keywords are the <keyword> s, bound to the specified transformers. Each
binding of a <keyword> has <body> as its region.
Note: let-syntax is available only after having required the file
"full-syntax" .
(let-syntax ((when (syntax-rules ()
((when test stmt1 stmt2 ...)
(if test
(begin stmt1
stmt2 ...))))))
(let ((if #t))
(when if (set! if 'now))
if)) ⇒ now
(let ((x 'outer))
(let-syntax ((m (syntax-rules () ((m) x))))
(let ((x 'inner))
(m)))) ⇒ outer
|
|
(letrec-syntax <bindings> <body>) | R5RS syntax |
Syntax of letrec-syntax is the same as for let-syntax .
The <body> is expanded in the syntactic environment obtained by
extending the syntactic environment of the letrec-syntax expression
with macros whose keywords are the <keyword> s, bound to the specified
transformers. Each binding of a <keyword> has the <bindings> as well
as the <body> within its region, so the transformers can transcribe
expressions into uses of the macros introduced by the letrec-syntax
expression.
Note: letrec-syntax is available only after having required the file
"full-syntax" .
(letrec-syntax
((my-or (syntax-rules ()
((my-or) #f)
((my-or e) e)
((my-or e1 e2 ...)
(let ((temp e1))
(if temp
temp
(my-or e2 ...)))))))
(let ((x #f)
(y 7)
(temp 8)
(let odd?)
(if even?))
(my-or x
(let temp)
(if y)
y))) ⇒ 7
|
|
(macro-expand form) | STklos procedure |
Returns the macro expansion of form if it is a macro call,
otherwise form is returned unchanged.
(define-macro (incr x) `(set! ,x (+ ,x 1)))
(macro-expand '(incr foo)) ⇒ (set! foo (+ foo 1))
(macro-expand '(car bar)) ⇒ (car bar)
|
|
1: Documentation about hygienic macros has been stolen in
the SLIB manual
|