Previous chapter: Assignment

Dylan manual: Conditionals, Flow of Control, and Evaluation

8. Conditionals, Flow of Control, and Evaluation

The following syntax forms, functions, and objects are used in performing tests and conditional operations in Dylan.

True and False

In Dylan, there is a single object that counts as false. This object is indicated by the syntax '#f' FN17 . All other values are considered true for the sake of true/false testing. The canonical true object, indicated by the syntax '#t', can be used for clarity of code.

Throughout the manual, the phrases true, false, returns true, and returns false are used. Its important to remember that returns true does not necessarily mean returns the object #t. It simply means returns any object besides #f. Sometimes for efficiency, debuggability, or informational value, some value besides #t is returned.

#t[return to first citation]		[Object]
This is the canonical true value. In Dylan, all objects besides #f count as true, not just #t.

#f[return to first citation]		[Object]
This is the only false value.

not   thing   ==>  boolean	[Function]
Returns true if thing is #f; otherwise returns false.

Conditional Execution

The following syntax forms are used to perform conditional execution.

if[return to first citation]   test consequent alternate   ==>  values	[Special Form]
if evaluates test. If test evaluates to a true value, consequent is evaluated. If test evaluates to #f (the only false value), then alternate is evaluated FN18 .

The values of the consequent or alternate form (whichever is evaluated) are returned.

? (define-method test ((thing <object>))
    (if thing
        #t
        #f))
test
? (test 'hello)
#t
? (test #t)
#t
? (test #f)
#f

? (define-method double-negative ((num <number>))
     (if (< num 0)
         (+ num num)
         num))
double-negative
? (double-negative 11)
11
? (double-negative -11)
-22
To place multiple forms in the consequent or alternate section of an if statement, group the forms with begin.

? (define-method show-and-tell ((thing <object>))
     (if thing
         (begin
            (print thing)
            #t)
         #f))
show-and-tell
? (show-and-tell "hello")
hello
#t
when   test  form1 form2...   ==>  values	[Macro]
when evaluates test. If test evaluates to a true value, all the forms are run, and the values of the last form are returned. If test evaluates to #f, then none of the forms are run and #f is returned.

If there are no forms, then #f is returned.

(when (bonus-illuminated? pinball post)
    (add-bonus-score current-player 100000))
unless   test  form1 form2...   ==>  values	[Macro]
unless evaluates test. If test evaluates to a true value, then none of the forms are evaluated and #f is returned. If test evaluates to #f, then all the forms are evaluated and the values of the last form are returned.

If there are no forms, then #f is returned.

(unless (detect-gas? nose)
    (light match))
cond	(test1 consequent1a consequent1b ...)	[Macro]
	(test2 consequent2a consequent2b ...) ...
==>  values
cond evaluates the tests in order. If a test returns true, the corresponding consequent- forms are evaluated and the values of the last one are returned. Subsequent tests are not evaluated.

If there are no consequent forms in the test that succeeds, then the first value of the test is returned.

If no test is true, then cond returns #f.

(cond ((< new-position old-position)
         "the new position is less")
       ((= new-position old-position)
        "the positions are equal")
       (else: "the new position is greater"))
It is common for the last test-and-consequent clause of a cond statement to be an otherwise clause. In this case, the convention is to use the keyword else: or the canonical true value #t to indicate a test that will always be true.

case	target-form	[Macro]
	(match-list1 consequent1a consequent1b ...)
	(match-list2 consequent2a consequent2b ...) ...
==>  values
Each match-list must be a list of objects FN19 or the keyword else:, or the value #t. The elements of the match-list are not evaluated.

When the case form is entered, the target-form is evaluated, generating a target value. The match-lists are then searched for an object which matches the target value. If a match is found, the corresponding consequent forms are evaluated and the values of the last consequent are returned. If there are no consequent forms in the matching clause, then #f is returned.

The match objects are compared to the target value with id?. Because id? is used, and because the match objects must be compile-time constants, the match objects will usually be numbers, characters, symbols, or keywords.

It is an error if the same match appears in more than one match-list.

As a special case, the keyword else: or the value #t may appear instead of a match-list. This clause will be considered a match if no other match is found.

If there is no matching clause, an error is signaled. Because an else: clause matches when no other clause matches, a case form that includes an else: clause will never signal an error for failure to match.

(case (career-choice student)
   ((art music drama)
    (print "Dont quit your day job.")
   ((literature history linguistics)
    (print "That really is fascinating."))
   ((science math engineering)
    (print "Say, can you fix my VCR?"))
   (else: "I wish you luck."))
See Also: Dylan Design Notes: Select Ordering (Clarification)

select	target-form test   	[Macro]
	(match-list1 consequent1a consequent1b ...)
	(match-list2 consequent2a consequent2b ...) ...
==>  values
select is structurally similar to case. It differs in two respects:

The equivalent to a Common Lisp typecase statement can be synthesized in Dylan by using select with a test of instance?

(select my-object instance?
  ((<window> <view> <rectangle>) "its a graphic object")
  ((<number> <list> <sequence>) "its something computational")
  (else: "Dont know what it is"))
or    form1 form2...   ==>  values	[Macro]
or evaluates the forms one at a time, from left to right. If any form besides the last one returns true as its first value, or immediately returns that value and does not evaluate any of the subsequent forms. If all the forms but the last one return #f, then or returns all the values of the last form.

and    form1 form2...   ==>  values	[Macro]
and evaluates the forms from left to right until one of them returns #f, or until all the forms are evaluated. and returns #f if any but the last form returns #f as its first value; otherwise it returns all the values of the last form. Once one of the forms returns #f, none of the subsequent forms are evaluated.

begin[return to first citation]    form1 form2...   ==>  values	[Special Form]
begin executes the forms sequentially. The values of the last form are returned. If there are no forms, #f is returned.

Many special forms (for example, bind and method) contain an implicit begin around their bodies. Programs only need to use begin explicitly in situations where a single form is expected, for example, in the consequent or alternate clause of an if statement.

? (if #t
      (print "it was true")
      #t
      #f)
error:  too many arguments to if.
? (if #t
      (begin (print "it was true")
             #t)
      #f)
"it was true"
#t
Iteration Constructs

See Also: Dylan Design Notes: For Loops (Change)

for	((var1 init1 step1) 	[Macro]
 (var2 init2 step2) ...)
(end-test result1 result2...) 
body-form1 body-form2...
==>  values
for is a general iteration macro.

Each (var init step) is called a variable clause. Each variable clause controls one variable through the iteration. A single pass through the iteration involves binding the vars to new values, evaluating the end-test, and then (if the end-test evaluates to #f) evaluating the body-forms. If end-test evaluates to true, the loop exits and the values produced by the evaluation of the result forms, if any, are returned. If there are no result forms, #f is returned.

The inits are evaluated to produce the values for the initial bindings of the vars. They are evaluated in a scope that does not include any of the iteration variables. The steps are evaluated to produce the values for the new bindings of the vars each time through the loop. The steps are evaluated in a scope that includes the previous values of each var.

The body-forms are used to produce side effects. It is common to write for statements that do not have any body forms. The following definition of factorial, for example, relies entirely on the variable clauses, end test, and result.

  (define-method factorial ((n <integer>))
     (for ((i n (- i 1))   ;variable clause 1
           (v 1 (* v i)))  ;variable clause 2
          ((<= i 0) v))    ;end test and result
for-each	((var1 collection1) 	[Macro]
	 (var2 collection2) ...)
	(end-test result1 result2...) 
	body-form1 body-form2...
==>  values
for-each is used for iterating over the elements of one or more collections. Collections are described in a separate section of this document.

At the outset, each collection is evaluated and should yield a collection. Then the iteration begins.

At the start of each pass through the iteration, each var is bound to the next element of the corresponding collection. If any collection is exhausted (contains no more elements), then the iteration is complete and for-each returns #f. Otherwise, the end-test is evaluated. If the end-test is true, the results are evaluated in order and the values of the last result are returned by for-each; if there are no results, #f is returned. If the end-test evaluates to #f, then the body-forms are evaluated, in order, and the iteration proceeds through another pass.

The end-test, results, and body-forms are evaluated in an environment in which the vars contain the most recent elements of the collections.

This example returns the first even number in a sequence, or #f if none of the numbers are even.

(define-method first-even ((s <sequence>))
  (for-each ((number s))
            ((even? number) number)
                             ; No body forms needed
     ))
This example takes a sequence of cities, and schedules an Olympic game in each one at 4-year intervals.

(define-method schedule-olympic-games ((cities <sequence>)
                                       (start-year <number>))
   (for-each ((year (range from: start-year by: 4))
              (city cities))
             ()              ; No end test needed.
      (schedule-game city year)))
dotimes   (var count result)  form1 form2...  ==>   values	[Macro]
dotimes begins by evaluating count-form, which should evaluate to an integer, called the count. It then evaluates the body forms once for each integer from zero (inclusive) to the count (exclusive). If the count was negative or zero, the body forms are not evaluated at all. When the iteration is complete, the result form is evaluated and the values produced are returned. result is optional; if there is no result form, then dotimes returns #f.

? (begin
    (dotimes (i 6) (print "bang!"))
    (print "click!"))
bang!
bang!
bang!
bang!
bang!
bang!
click!
while test form1 form2... ==> #f [Macro]

while evaluates the forms over and over until the test returns false.

Each pass through the loop begins by evaluating test. If test evaluates to a true value, the forms are evaluated and the looping continues. If test evaluates to #f, the loop terminates and while returns #f.

until   test  form1 form2...   ==>  #f	[Macro]
until evaluates the forms over and over until the test returns true.

Each pass through the loop begins by evaluating test. If test evaluates to #f, the forms are evaluated and the looping continues. If test evaluates to a true value, the loop terminates and until returns #f.

See Also: Dylan Design Notes: Exit Extent (Change)

Simple Exits

bind-exit[return to first citation]   (exit-variable) form1 form2...   ==>  values	[Special Form]
binds exit-variable to an exit procedure, and executes forms. If the exit procedure is never called, a normal exit occurs and the values of the last form are returned by bind-exit; if there are no forms, #f is returned. FN20

At any point in time before the last form returns, the exit procedure can be called. Calling the exit procedure has the effect of immediately exiting the bind-exit. The exit procedure accepts any number of arguments. These arguments are used as the return values of bind-exit. Calling an exit procedure is known as performing a non-local exit.

exit-variable should be a variable name. It is bound as a lexical variable within the bind-exit form.

The exit procedure is a first-class value. Specifically, it can be passed as an argument to functions, stored in data structures, etc. Its use is not restricted to the lexical body of the bind-exit form. However, the exit procedure is valid only for the dynamic extent of the bind-exit form. It is an error to call an exit procedure after the bind-exit form that created it returns. FN21

The following example shows how bind-exit is used to generate an escape procedure, which is used to terminate a loop. The function returns the first even number in a sequence. (An alternate definition of first-even is given in the description of for-each.)

? (define-method first-even ((seq <sequence>))
    (bind-exit (exit)
      (do (method (item)
             (when (even? item)
               (exit item)))
           seq)))
first-even
? (first-even '(1 3 5 4 7 9 10))
4
unwind-protect[return to first citation]   protected-form cleanup-form1 cleanup-form2...   	[Macro]
==>  values
unwind-protect executes the protected-form and then the cleanup-forms. The cleanup-forms are guaranteed to be evaluated, even if the protected-forms are terminated by a non-local exit.

In the case of a normal exit, unwind-protect returns the values returned by the protected-form.

Evaluation

quote[return to first citation]   object   ==>  object	[Special Form]
quote returns object. object is not evaluated.

quote is usually abbreviated with the reader macro '.

? +
{the generic function +}
? '+
+
? (quote +)
+
? ''+
(quote +)
? (+ 10 10)
20
? '(+ 10 10)
(+ 10 10)
? (quote (+ 10 10))
(+ 10 10)
apply[return to first citation] function #rest args ==> values [Function]

apply calls function with args as the arguments. The last arg must be a sequence. The elements of the sequence are spread out and taken as individual arguments to function.

? (apply + 1 '(2 3))
6
? (+ 1 2 3)
6
? (define math-functions (list + * / ))
math-functions
? math-functions
({method +} {method *} {method /} {method })
? (first math-functions)
{method +}
? (apply (first math-functions) 1 2 '(3 4))
10

Next chapter: Equality and Magnitude Comparisons