QforMortals/execution control

From Kx Wiki
Jump to: navigation, search

Contents

Execution Control

Overview

Functions provide sequential evaluation of a series of expressions. In this chapter, we demonstrate how to exert more control over execution in q.

Control Flow

In a vector-oriented language such as q, best performance is generally obtained by avoiding loops and individual tests. For those times when you simply must use an "if" statement or a loop, q has versions of the usual constructs.

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, which has the form,

$[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

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.

The same effect can be achieved in q using an extended form 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.

Note that any conditional other than the first is only evaluated if all those prior to it have evaluated to zero. In addition, any 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 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;...]]

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 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

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 and 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 exprcond is evaluated and the expressions expr1 thru exprn are evaluated 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 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 values 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 amend (:) 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 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.

	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 passes to the catch block where the exception can be handled or pushed up to the caller. This mechanism allows the call stack to be unwound gracefully.

A similar capability exists in q using triadic forms of function evaluation (@) and (.). Triadic @ is used for monadic functions and triadic . is used for multivalent functions. The syntax is the same 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, any return value is returned from the protected evaluation. Should an error arise, exprfail is evaluated.

Warning.png 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 simply 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. Instead, you can 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 key 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
	q))show zs f                   / see what's what
d| .
P| x
L| a
G|  b
D| {a:6;x+a*b}

Sometimes, stopping execution prior to the offending expression is helpful. This can be done be 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 form of break point.

However the execution is suspended, you can evluate the expressions of the function by hand from the console. To return from the function with a value, issue a return (:) with the value from the command line. To return an error, issue a signal (') from the command line. To terminate execution and empty 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. Global entities created in the script exist in the workspace after the script is loaded.

A script can be loaded at the start of the q session, or at any time during the session using the load (\l) command. The load command can be executed from the console or from another script.

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 white space. Any continuation lines must be indented, meaning that there is at least one white space 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.

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

Here is the script file for the first sample q program introduced.

sample:{
 t:("DSF";enlist ",") 0: `:c:/q/data/px.csv;
 tmpx:select mpx:max Price by Date,Sym from t;
 h:hopen `:aerowing:5042;
 rtmpx:h "select mpx:max Price by Date,Sym from tpx";
 hclose h;
 .[`:c:/q/data/tpx.dat;();,;rtmpx,tmpx]}

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

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

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox