QforMortals2/execution control

From Kx Wiki
Jump to: navigation, search

Contents

Execution Control

Overview

Function evaluation provides sequential execution of a series of expressions. In this chapter, we demonstrate how to control execution in q.

Control Flow

In a vector-oriented language such as q, the clearest code and best performance is generally obtained by avoiding loops and individual tests. For those times when you simply must write iffy or loopy code, q has versions of the usual constructs.

Warning.png Warning: The constructs in this section all involve branching in the byte code that is generated by the q interpreter. The offset of the branch destination is limited (currently to 255), which means that the sequence of q expressions that can be contained in any part of $, if, do, or while must be short. At some point, insertion of one additional statement will result in a branch error, which is q's way of rejecting bloated code. If you insist on writing iffy or loopy code (never a good idea in q), factor code blocks into separate functions.

Basic Conditional Evaluation

Languages of C heritage have a form of in-line 'if' called conditional evaluation that has the form.

exprcond ? exprtrue : exprfalse

where exprcond is an expression that evaluates to a boolean (or int in C and C++). The result of the expression is exprtrue when exprcond is true (or non-zero) and exprfalse otherwise.

The same effect can be achieved in q using basic conditional evaluation,

$[exprcond;exprtrue;exprfalse]

where exprcond is an expression that evaluates to a boolean or int. The result is exprtrue when exprcond is not zero and exprfalse if it is zero.

        a:42
        b:98
        $[a>60;`Pass;`Fail]
`Fail
        $[b>60;`Pass;`Fail]
`Pass

Observe that a test for zero in exprcond can be abbreviated.

        c:0
        $[a;`Nonzero;`Zero]
`Nonzero
        $[b;`Nonzero;`Zero]
`Nonzero
        $[c;`Nonzero;`Zero]
`Zero
Warning.png Note: A null is not accepted for exprcond.
        d:0N
        $[d;`NonNull;`Null]
'type

Extended Conditional Evaluation

In languages of C heritage, the if-else construct has the form,

if (exprcond) {
statementtrue1;
.
.
.
}
else {
statementfalse1;
.
.
.
}

where exprcond is an expression that evaluates to a boolean (or int in C and C++). If the expression exprcond is true (or non-zero) the first sequence of statements in braces is executed; otherwise, the second sequence of statements in braces is executed.

A similar effect can be achieved in q using an extended form of conditional evaluation.

$[exprcond;[exprtrue1;...];[exprfalse1;...]]

where exprcond is an expression that evaluates to a boolean or int. When exprcond evaluates to non-zero, the first bracketed sequence of expressions is executed in left-to-right order; otherwise, the second bracketed sequence of expression is executed.

        a1:42
        a2:24
        $[a1<>42;[a:6;b:7;a*b];[a:`Life;b:`the;c:`Universe;a,b,c]]
`Life`the`Universe

        $[a2<>42;[a:6;b:7;a*b];[a:`Life;b:`the;c:`Universe;a,b,c]]
42

Languages of C heritage have a cascading form of if-else in which multiple tests can be made,

if (exprcond1) {
statementtrue11;
.
.
.
}
else if (exprcondn) {
statementtruen1;
.
.
.
}
.
.
.
else {
statementfalse;
.
.
.
}

In this construction, the exprcond are evaluated consecutively until one is true (or non-zero), at which point the associated block of statements is executed and the statement is complete. If none of the expressions passes, the final block of statements, called the default case, is executed.

Note that any conditional other than the first is only evaluated if all those prior to it have evaluated to false. In addition, only one of the statement blocks will be executed.

A similar effect can be achieved in q with another extended form of conditional execution.

$[exprcond1;exprtrue1; ... ;exprcondn;exprtruen;exprfalse]

In this form, the conditional expressions are evaluated consecutively until one is non-zero, at which point the associated exprtrue is evaluated and its result is returned. If none of the conditional expressions evaluates to non-zero, exprfalse is evaluated and its result is returned. Observe that exprfalse is distinguished as the last expression following a sequence of paired expressions.

Warning.png Note: Any conditional other than the first is only evaluated if all those prior to it have evaluated to zero. Otherwise put, a conditional evaluating to non-zero short-circuits the evaluation of all those after it.
         a:42
         b:0
         c:-42
         $[a=0;`zero;a>0;`pos;`neg]
`pos
         $[b=0;`zero;b>0;`pos;`neg]
`zero
         $[c=0;`zero;c>0;`pos;`neg]
`neg

Finally, the previous extended form of conditional execution can be further extended by substituting a bracketed sequence of expressions for any exprtrue or exprfalse.

$[exprcond1;[exprtrue11;...]; ... ; exprcondn;[exprtruen1;...];[exprfalse1;...]]

9.1.3 Vector Conditional Evaluation

Triadic vector-conditional evaluation ( ? ) has the form,

?[vb; exprtrue ; exprfalse]

where vb is a simple boolean list and exprtrue and exprfalse are atoms or vectors of the same type that conform to vb. The result conforms to vb, and contains exprtrue in positions where vb has 1b and exprfalse in positions where vb has 0b .

The following example inserts 42 for odd-valued items of a list.

        L:(til 10) mod 3
        L
0 1 2 0 1 2 0 1 2 0

        ?[0=L mod 2;L;42]
0 42 2 0 42 2 0 42 2 0
Warning.png Note: All arguments of a vector-conditional are fully executed. In other words, there is no short circuiting of the evaluation.

if

The if statement conditionally evaluates a sequence of expressions. It has the form,

if[exprcond;expr1; ... ;exprn]

where exprcond is evaluated and if it is non-zero the expressions expr1 thru exprn are evaluated in left-to-right order. The if statement does not have an explicit result.

For example,

        a:42
        b:98
        z:""
        if[a=42;z:"Life the universe and everything"]
        z
"Life the universe and everything"

        if[b<>42;x:6;y:7;z:x*y]
        z
42

do

The do statement is an iterator of the form,

do[exprcount; expr1; ... ; exprn]

where exprcount must evaluate to an int. The expressions expr1 thru exprn are evaluated exprcount times in left-to-right order. The do statement does not have an explicit result.

For example, the following expression computes n factorial. It iterates n-1 times, decrementing the factor f on each pass.

        n:5
        do[-1+f:r:n;r*:f-:1]
        r
120

while

The while statement is an iterator of the form,

while['exprcond;expr1; ... ; exprn]

where expr cond is evaluated and the expressions expr1 thru exprn are evaluated repeatedly in left-to-right order as long as exprcond is non-zero. The while statement does not have an explicit result.

Let's examine a nifty example taken from the Q Language Reference Manual. The following function returns a list in which each null item in the argument list x has been replaced with the item before it.

        f:{r:x;r[i]:r[-1+i:where null r];r}

Now observe that the expression,

        max null v

indicates whether there are any nulls in a list v (why?).

The following expression applies f iteratively until there are no nulls left in v.

        while[max null v;v:f v]

Effectively, non-null values are propagated forward across nulls.

        v:10 -3.1 0n 42 0n 0n 0n 3.4
        while[max null v;v:f v]
        v
10 -3.1 -3.1 42 42 42 42 3.4

Do you see the problem with this example? Hint: consider the case where v has one or more initial null items and remember that Ctrl-C terminates execution of a long-running q expression. The while expression will iterate forever because there is no value to propagate across the initial item.

When you know v will be of a type having an underlying numeric value, one solution is to prepend a default initial value and remove it afterward. We use a type-matched zero,

        v:0n -3.1 0n 42 0n 0n 0n 3.0
        w:((type v)$0),v
        while[max null w;w:f w]
        1_w
0 -3.1 -3.1 42 42 42 42 3

Return and Signal

Normal function execution evaluates each expression in the function and terminates after the last one. There are two mechanisms for ending the execution early: one returns successfully and the other aborts.

To terminate a function's execution successfully and return a value, use an empty assignment, which is assign ( : ) with a value to its right and no variable to its left. For example, in the following contrived function, execution is terminated and the result is returned after the third expression. The final expression is never evaluated.

        c:0
       f:{a:6;b:7;:a*b;c::98}
       f 0
42
       c
0

To abort function execution immediately, use signal, which is single-quote ( ' ) with a value to its right. For example, in the following function, execution will be aborted in the third expression. The final expression that assigns c is never evaluated.

        c:0
        g:{a:6;b:7;'`TheEnd;c::98}
        g 0
{a:6;b:7;'`TheEnd;c::98}
'TheEnd

        c
0
Warning.png Note: Unless a function issuing a signal is invoked with protected execution, the signal will cause the calling routine to fail.

You can also use signal within an if statement to terminate execution. Compare the following,

        a:42
        if[a<50; '`Stop; b:100]
'Stop

Protected Evaluation

Languages of C++ heritage have the concept of protected execution using a try-catch. The idea is that an unexpected condition arising from any statement enclosed in the try portion does not abort execution. Instead, control transfers to the catch block, where the exception can be handled or passed up to the caller. This mechanism allows the call stack to be unwound gracefully.

Q provides a similar capability using triadic forms of function evaluation ( @ ) and ( . ). Triadic @ is used for monadic functions and triadic . is used for multivalent functions. The syntax is the similar for both,

@[fmon;a;exprfail]
.[fmul;Largs;exprfail]

Here fmon is a monadic function, a is single argument, fmul is a multivalent function, Largs is a list of arguments, and exprfail is any expression. In both forms, the function is applied to its argument(s). Provided there is no error in evaluating the function, the return value of f is returned from the protected evaluation. Should an error arise, exprfail is evaluated.

Warning.png Note: If exprfail results in an error, the protected call itself will fail.

These functions are especially useful when processing input received from users. In the following examples, you would replace the unhelpful error message with more useful error handling.

Suppose a user wishes to enter dynamic q expressions. You could place the expression in a string and pass it to value. The problem with this is that if the user types an invalid q expression, it will cause the application to fail. You should instead use protected execution.

        s:"6*7"
        @[value;s;`$"Invalid q expression"]
42

        s:"6x7"
        @[value;s;`$"Invalid q expression"]
`Invalid q expression

Similarly, triadic . provides protected execution for multivalent functions.

        x:6
        y:7
        .[*;(x;y);`$" Invalid args for *"]
42
        x:6
        y:`7
        .[*;(x;y);`$" Invalid args for *"]
`Invalid args for *

Debugging

Debugging in q harkens back to the olden days, before the advent of debuggers and integrated development environments. The q gods don't give debugging much consideration because their code always runs correctly the first time. For the rest of us, things aren't quite as bad as inserting print statements, but you are certainly on your own. There is no debugger, nor is there any notion of break points or tracing execution.

When any expression evaluation fails, the console displays an (often cryptic) error message along with a dump of the offending values. Many errors manifest as either 'type or 'length, indicating an incompatibility in function arguments with respect to type or length. The goal is to discover the root cause of the superficial error.

The first step is to examine the dump of the offending arguments. Sometimes, the error will be obvious. A common 'type culprit is violation of type checking by attempting to assign a non-matching value to a simple list (e.g., a table column). Another common 'type offense is attempting to perform an operation on an atom not in the domain of the operation. A common culprit is failure to enlist an argument when a list is expected.

In a technique passed on by Simon Garland, you can get a more useful display of relevant information when a function is suspended. Define a function, say zs, as follows,

        zs:{`d`P`L`G`D!(system"d"),v[1 2 3],enlist last v:value x}

This function takes another function as its argument and returns a dictionary with entries for the current directory, function parameters, local variables referenced, global variables referenced and the function definition.

We demonstrate this with a trivial example.

        b:7
        f:{a:6;x+a*b}

        f[100]                / this is OK
142
        f[`100]                / this is an error
{a:6;x+a*b}
'type
+
`00
42
        zs f                / see what's what
d| `.
P| ,`x
L| ,`a
G| ``b
D| "{a:6;x+a*b}"

Stopping execution prior to the offending expression is helpful. This can be done by inserting a signal before the expression you wish to examine. You can then evaluate the various items in the offending evaluation. Stopping execution with a signal is a poor man's break point.

However the execution is suspended, you can evaluate the expressions of the function by hand from the console. To resume execution with a return value, issue a return ( : ) with the desired value at the command prompt. To return an error, issue a signal ( ' ) from the command line. To terminate execution and clear the call stack, issue ( \ ) from the command line.

Scripts

A script is a q program stored in a text file with an extension of 'q'. A script can contain any q expressions or commands. The contents of the script are executed sequentially from top to bottom. Non-local entities created in the script exist in the workspace after the script is loaded.

Creating and Loading a Script

You can create a script in a text editor and save it with a q extension. For example, enter the following lines and save to a file named trades.q in the q directory.

        trades:([] sym:(); ex:(); time:(); price:())
        `trades insert (`IBM;`N; 12:10:00.0; 82.1)
        `trades insert (`IBM;`O; 12:30:00.0; 81.95)
        `trades insert (`MSFT;`N; 12:45:00.0; 23.45)
        `trades insert (`IBM;`N; 12:50:00.0; 82.05)
        `trades insert (`MSFT;`N; 13:30:00.0; 23.40)

Now issue the load command,

        \l trades.q
,0
,1
,2
,3
,4

You can verify that the trades table has been created and the records have been inserted.

       count trades
5

A script can be loaded at the start of the q session, or at any time during the session using the \l command. The load command can be executed from the console or from another script. See here for more on commands.

Special Notations

You can comment out a block of code by surrounding it matching / and \. An unmatched \ exits the script.

Multi-line expressions are permitted in a script but they have a special form. The first line must be out-dented, meaning that it begins at the left of the line with no initial whitespace. Any continuation lines must be indented, meaning that there is at least one whitespace character at the beginning of the line. Empty lines between expressions are permitted.

Table definition syntax and function definition syntax have the same rule for splitting across multiple lines:

A table or function can have line breaks after the closing square bracket or after a semicolon separator (;).

Passing Parameters

Parameters are passed to a q script at q startup similarly to command line parameters in a C or Java program. They are strings that are not explicitly declared and are accessed positionally corresponding to the order in which they are passed.

Warning.png Note: As of this writing (Jun 2007), parameters can be passed when a script is loaded at q startup but not when a script is loaded with the \l command.

Specifically, the system variable .z.x is a list of strings, each of which contains the char representation of an argument present when the script was invoked. For example, the script captureargs.q,

         / script that captures its first three arguments
        p0:.z.x 0;
        p1:.z.x 1;
        p2:.z.x 2;

can be loaded during q startup,

        q.exe captureargs.q 42 forty 2.0

and in the new q session you will find,

	p0
"42"
	p1
"forty"
	p2
"2.0"

Example

Here is the commented script text for the sample program from Overview.

        / read px.csv file into table t
        t:("DSF"; enlist ",") 0: `:c:/q/data/px.csv;

        / select max Price from t grouped by Date and Sym
        tmpx:select mpx:max Price by Date,Sym from t;

        / open connection to q process on port 5042 on aerowing
        h:hopen `:aerowing:5042;

        / issue above query against table tpx on remote machine
        rtmpx:h "select mpx:max Price by Date, Sym from tpx";

        / close connection
        hclose h;

        / append merger of local and remote results to file tpx.dat
        .[`:c:/q/data/tpx.dat; (); ,; rtmpx,tmpx]

Prev: Queries: q-sql Next: I/O

Table of Contents

©2006-2007 Kx Systems, Inc. and Continuux LLC. All rights reserved.

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox