2. Expressions
This chapter describes the main forms available in STklos. R5RS constructions are given very succinctly here for reference. See [R5RS] for a complete description.
2.1. Literal expressions
R5RS syntax
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
R5RS requires to quote constant lists and constant vectors. This is not necessary with STklos. |
2.2. Procedures
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, otherwisevalue
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, otherwisevalue
will be stored in it. Furthermore,test?
will be given the value#t
if a value is passed for the given variable, otherwisetest?
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)
STklos procedure
Returns #t
if obj
is a procedure created with the lambda
syntax and #f
otherwise.
Note that primitive procedures (those which are written in C) are not closures:
(define (cube x) (* x x x))
(closure? cube) => #t
(define square-root sqrt)
(eq? square-root sqrt) => #t
(closure? square-root) => #f
(closure? 10) => #f
(closure? display) => #f
(closure? (lambda (x) (- x))) => #t
(closure? any) => #t
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
R5RS syntax
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,
(set! (vector-ref x i) v)
would be equivalent to:
(vector-set! x i v)
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.
STklos 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:
(set! (setter p) s)
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)
(set! (list-ref x i) v) == (list-set! x i v)
Furhermore, Parameter Objects (see Section 4.21) are their own setter:
(real-precision) => 15
(set! (real-precision) 12)
(real-precision) => 12
2.4. Conditionals
R5RS syntax
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
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
(<test> => <expression>)
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
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
R5RS syntax
The <testi>
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
R5RS syntax
The <testi>
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)
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.
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.
R5RS syntax
In a let
, <bindings>
should have the form
((<variable1> <init1>) ...)
where each <initi>
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
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))
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
(<variable> <init>)
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
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
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
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)
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)
R7RS procedure
The form define-values
creates multiple definitions from a single expression
returning multiple values. Here, expression
is evaluated, and the formals
are bound to the return values in the same way that the formals
in a
lambda expression are matched to the arguments in a procedure call.
(let ()
(define-values (x y) (exact-integer-sqrt 17))
(list x y)) => (4 1)
(let ()
(define-values (x y) (values 1 2))
(+ x y)) => 3
(let ()
(define-values (x . y) (values 1 2 3))
(list x y) => (1 (2 3))
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
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
STklos syntax
The <expression>
s are evaluated sequentially from left to right,
and the value(s) of the last <expression> is(are) returned as in a
begin
form. Within a tagbody
form expressions which are keywords
are considered as tags and the special form (→ tag)
is used to
transfer execution to the given tag. This is a very low level
form which is inspired on tabgody
Common Lisp’s form. It can be useful
for defining new syntaxes, and should probably not be used as is.
(tagbody ;; an infinite loop
#:1 (display ".")
(-> #:1))
(let ((v 0))
(tagbody
#:top (when (< v 5)
(display v)
(set! v (fx+ v 1))
(-> #:top)))) |- 01234
(tagbody (display 1)
(tagbody (display 2)
(-> #:inner)
(display "not printed")
#:inner
(display 3)
(-> #:outer)
(display "not printed too"))
#:outer
(display "4")) |- 1234
2.7. Iterations
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
(<var> <init> <var>)
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
STklos syntax
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)
STklos syntax
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). The result of
repeat
is undefined.
This form could be easily simulated with dotimes
. Its interest is
that it is faster.
(repeat 3 (display ".")) => prints "..."
(repeat 0 (display ".")) => prints nothing
STklos syntax
While
evaluates the <expression>
s until <test>
returns a false
value. The value returned by this form is void.
STklos syntax
Until
evaluates the <expression>
s until <while>
returns a false
value. The value returned by this form is void.
STklos procedure
This macro will iterate over lst
, executing body
for each
element x
. The symbol x
will be captured and bound in body
.
If result
is provided, and is not x
, this form returns result
;
otherwise, its result is undefined.
(dolist (x '(10 20 30))
(display (- x)) (newline))
-10
-20
-30
(let ((sum 0))
(dolist (x '(1 2 3 4 5) sum)
(inc! sum (square x)))) => 55
The form dolist
is borrowed from Common Lisp. However, using dolist
is not the preferred way to process a list. For instance, the last example
could be better rewritten in:
(apply + (map square '(1 2 3 4 5))
2.8. Delayed Evaluation
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
for a more complete
description of delay
.
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.
The special form delay-force
appears with name lazy
in SRFI-45 (Primitives for Expressing Iterative Lazy Algorithms).
R5RS procedure
Forces the value of promise
(see primitive 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
See R5RS for details on a posssible way to implement
force and delay .
|
R7RS procedure
Returns #t
if obj
is a promise, otherwise returns #f
.
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.
The primitve make-promise
appears with name eager
in
SRFI-45.
2.9. Quasiquotation
R5RS syntax
"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)) . ,(car '(cons)))
=> ((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
(require "full-syntax")
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.
STklos syntax
define-macro
can be used to define low-level macro
(i.e. ,(emph "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)
R5RS syntax
<Define-syntax>
extends the top-level syntactic environment by binding
the <identifier>
to the specified transformer.
<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 ...))))
R5RS syntax
<literals>
is a list of identifiers, and each <syntax-rule>
should be of
the form
(pattern template)
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.
For a complete description of the Scheme pattern language, refer to R5RS. |
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.
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
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.
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
STklos procedure
macro-expand
returns the macro expansion of form
if it is a macro call,
otherwise form
is returned unchanged.
(define-macro (add1 x) `(+ ,x 1))
(macro-expand '(add1 foo)) => (+ foo 1)
(macro-expand '(car bar)) => (car bar)
macro-expand
returns the full macro expansion of form
, that is it repeats
the macro-expansion, while the expanded form contains macro calls.
(define-macro (add2 x) `(add1 (add1 ,x)))
(macro-expand '(add2 foo)) => (add1 (add1 foo))
(macro-expand* '(add2 foo)) => (+ (+ foo 1) 1)
macro-expand and macro-expand* expand only the global macros.
|