11. Control
11.1 Output printing
When an interactive programming system has done evaluating a top-level
expression, it normally prints the result, followed by a new-line.
Evaluating a shell command does not yield an explicit result,
but prints the standard output of the program.
Shells assume that a program prints complete lines, terminated
by new-lines, so an explicit new-line is not added by the shell.
So if Q views the output from a program as
equivalent to returning a string, then when printing a result
that is a string, we should not add a new-line. But when
printing other objects, it is desirable to add a new-line.
The format directive `%+s' prints the argument as suitable
for printing a result. If the argument is not a sequence, it is
printed, followed by a new-line.
| sprintf "|%+s|" 3+4 ==> "|7\n|"
|
To print a sequence, each element of the sequence is printed in turn.
Spaces are normally printed between elements.
However, if neighboring elements are in turn sequences,
a new-line is printed instead.
Also, no space is printed after a character. (Since a string
is a character sequence, it is printed without extra spaces.)
Finally, a new-line is printed, unless the sequence was empty,
or the last element was a character.
11.2 True and false
Statements that are evaluated solely for their side-effects
(such as assignments and declarations) should not print anything.
Therefore, we decree that they return "nothing",
that is the empty string ""
.
A declaration in Q is just a unification
expression. (That is, there is no conceptual difference
between 5=(2+3)
, and :x=(2+3)
, except that the latter
also declares x
(a compile-time action), and at run-time
has the side-effect of binding x
to 5
.)
Hence, a successful unification retrurn ""
as its value.
For consistency, other standard relations (such as
2<3
) also evaluate to ""
if they succeed.
The default "true" value in Q is therefore ""
.
Note also that ;
is essentially "logical and."
"Exception" values represent "false."
If a function argument is an exception, the function
returns the exception. Exceptions are not normal first-class
values, and they use a condition-handling
mechanism using non-local jumps.
An "uncaught" exception is normally just printed:
| Q1> 2<3 # True, prints nothing.
Q2> 2>3 # False, raises exception.
Exception: Failed comparison (>).
|
Exceptions that represent failures (but not serious errors)
are caught using the if
construct.
| Q3> if 2 > 3 => "True\n" || "False\n"
False
|
If a program returns a non-zero exit code, that raises
a failure exception:
| Q4> sh -c "exit 1"
Exception: Program sh (pid: 5292) failed with return code 1.
Q5> if sh -c "exit 1" => "sh ok\n" || "sh failed\n"
sh failed
|
(One difference compared to traditional shells is that in Q
a statement list only succeeds if each statement succeeds.
That is, in Q, the ;
operator corresponds to `sh''s &&
.)
11.3 Statement lists
Most languages support some kind of "statement list" form, where each
"statement" is evaluated in turn.
In Q, we define the effect of evaluating expr1; expr2
as the following:
First evaluate expr1, then evaluate expr2.
If either result is ""
(or []
),
then return the other result.
If neither is null, the result is the concatenation of the two values,
as if computed by sprintf "%s%s" expr1 expr2
.
This arcane definition is chosen because it has the following
desirable properties:
-
The
let
common in functional languages:
| (let ((x (+ 3 4))) (* x x)) # Scheme example
|
(which evaluates to 49) is expressible as:
This works because the value of :x=3+4
is []
.
-
Some people perfer a
where
form,
where the definitions are given after their use:
| x*x where x = 3+4 # Not Q
|
This is expressible as:
-
The output from a statement
list is the concatenation of the output from
each sub-command. For example:
| :foo = (cat file1; cat file2)
|
makes foo
a string of the concatenation of file1
and file2
.
This is consistent with how shells work.
-
Associativity:
(a; b); c
= a; (b; c)
-
"";a
= a;""
= a
-
("string1"; "string2")
= "string1string2"
-
sprintf "%s" (a;b)
= (sprintf "%s" a; sprintf "%s" b)
We also define:
to be the same as:
11.4 Looping
Q (being a non-pure functional language) does not have standard
iterative constructs. Instead it relies on various operators
for manipulating sequences. The intent is to optimize these
in a manner similar to [Water's streams].
A very powerful primitive is provided by the {...?...}
construct, which is a lazy (demand-evaluated) sequence defined
as a mapping from an index (denoted by ?
) to the
value of the expression in {...}
.
| Q12> ({stdout printf "<%d>" ?} for 10) do; stdout printf "\n"
<0><1><2><3><4><5><6><7><8><9>
|
The do
operator takes any sequence (lazy or not),
and "prints" each element in sequence. (Normally each
element evaluates to an empty result, but the side effect is
of interest.)
We informally define X do
for a sequence X
of length n
as:
| sprintf "%s" (X 0; X 1; ...; X n-1)
|
(Of course n
need not be known before X
is exhausted.)
Thus we can replace `printf' operations that write to `stdout'
with ones that returns a string:
| Q2> ({sprintf "<%d>" ?} for 10) do; "\n"
<0><1><2><3><4><5><6><7><8><9>
|
This document was generated
by Per Bothner on December, 4 2001
using texi2html