4. Operators
4.0 Operators Are Functions
Old-time purists called q operators "verbs" as did we in previous versions of this text. Reading the expression 2+3 right-to-left as "add 3 to 2," the operand 3 is a noun (subject), the operator + is a verb and the operand 2 is a noun (object).
We shall use the terms operator and function interchangeably but deprecate the use of "verb." See Chapter 6 for more on this but for now we just note that at times q distinguishes between functions used as nouns or verbs.
For Multi-threading (Advanced)
We place this here for easy reference throughout the remainder of the tutorial. As of q4.0 the following built in operations are multithreaded and will take advantage of available secondary threads and multi-channel memory to perform parallel processing when appropriate. Other than starting q with secondary threads, no other adjustment to the program need be made. See the KX website for details.
| # (take) | $ (cast) | % | & | * |
|---|---|---|---|---|
| + | , (join) | - | < | <= |
| <> | = | > | >= | ? (find) |
| @ (at) | _ (cut) | _ (drop) | abs | acos |
| aj | all | an | and | any |
| asin | asof | atan | avg | bin |
| binr | ceiling | cor | cos | cov |
| delete | deltas | dev | differ | distinct |
| div | exp | floor | ij | in |
| lj | log | max | min | mod |
| neg | next | not | null | or |
| prev | reciprocal | scov | sdev | select |
| select...by | signum | sin | sqrtt | sublist |
| sum | svar | til | uj | var |
| wavg | where | within | xbar | xexp |
| xlog | xprev |
4.0.1 Function Notation
Operators are built-in functions used with infix notation. We examine functions in depth in Chapter 6, but cover some salient points here. There are two main differences between the functions we can write and q's built-in functions.
-
Our functions must have alphanumeric names whereas q functions can have symbolic names.
-
Our functions can only be used in prefix notation whereas q functions can be used prefix or infix.
Terminology
The Powers That Be have adopted on the KX site the terminology "unary" for a function of one variable, "binary" for a function of two variables and "ternary" for a function of three variables. This erases the APL heritage of "monadic", "dyadic" and "triadic". We also now grudgingly follow this convention but smile every time we see the use of variadic in the KX Documentation. You will not see variadic here since we think it sounds like a medical diagnosis whereas multivalent is mellifluous.
Basic function application in q uses square brackets to enclose the arguments, and semicolons to separate multiple arguments. Thus the output value of a unary function f for the input x is written f[x]. We can omit the brackets for unary application and write f x. Application of a binary function g on arguments x and y is written g[x;y] in prefix or x g y in infix.
An atomic function acts recursively on data structures. More precisely, applying it to a list is the same as applying it to each item inside the list all the way down. We shall examine this in more detail in Chapter 5.
4.0.2 Primitives, Operators and Functional Notation
The normal way of writing addition in mathematics and most programming languages uses an operator with infix notation - e.g., addition is written with a plus symbol between the two operands.
2+3
In q, we can write addition this way and read it right-to-left as "add 3 to 2."
q)2+3
5
A q binary function written with infix notation is also called an operator. This terminology arises from the fact that the types of the operands and the result are all the same. Although some q originalists will insist on calling this a verb, we shall not.
The primitive operators are built-in functions, including the basic arithmetic, relation and comparison operators. Some are represented by a single ASCII symbol such as +, -, =, and <. Others use compound symbols, such as <=, >=, and <>. Still others have names such as not or neg.
Any operator can also be used with regular function notation. For example, we can also use + as a binary function that takes two numeric arguments and returns a numeric result. You probably wouldn't think twice at seeing sum[a;b] but you might blink at the following perfectly logical equivalent.
q)+[2;3]
5
Newbies will definitely need to get accustomed to,
q)=[2;3]
0b
It is even possible to write a binary operator using a combination of infix and functional notation. This may look strange, even to initiates.
q)(2+)[3]
5
q)(2+)3
5
4.0.3 Extension of Atomic Functions
A fundamental feature of atomic functions is that their action extends automatically into the items in a list. More precisely, an atomic function is characterized by the ith result of applying it to a list being the same as applying it to the ith item of the list. Of course, if you want to combine two lists they must be of the same length.
q)neg 1 2 3
-1 -2 -3
q)1 2 3+10 20 30
11 22 33
q)1 2 3+10 20 30 40
'length
This applies to nested lists as well, provided they conform in shape for multivalent operations.
q)neg (1 2 3; 4 5)
-1 -2 -3
-4 -5
q)(1 2 3; 4 5)+(100 200 300; 400 500)
101 202 303
404 505
Another fundamental property of atomic operators is that they implicitly extend atom arguments to match lists.
q)100+1 2 3
101 102 103
q)1 2 3+100
101 102 103
Atom extension also applies with nested lists.
q)100+(1 2 3; 4 5)
101 102 103
104 105
q)(1 2 3; 4 5)+100
101 102 103
104 105
4.1 Operator Precedence
There is none.
4.1.1 Traditional Operator Precedence
Mathematical operators in most programming languages have a concept of operator precedence, which attempts to resolve ambiguities in the evaluation of arithmetic and logical operations in expressions without parentheses. The arithmetic precedence rules were drummed into you in elementary school: multiplication and division are equal and come before addition and subtraction, etc. There are similar precedence rules for =, <, >, and and or.
4.1.2 Left-of-Right Evaluation
Although the traditional notion of operator precedence has the weight of incumbency (not to mention the imprecations of your fifth grade math teacher), it's time to throw the bum out. As mentioned in Chapter 1, q has no rules for operator precedence. Instead, it has one simple rule for evaluating any expression:
Expressions are evaluated left-of-right, which equates to right-to-left
Please review the Mathematics Refresher if the notion of left-of-right is new to you. The short version is that the composite f(g(x)), read "f of g of x" is evaluated by substituting x into g and then substituting that result into f. Evaluating the inner function first actually becomes right-to-left. Thinking functionally makes "of" a paradigm not just a preposition.
The adoption of left-of-right evaluation frees q to treat expression evaluation simply and uniformly without redundant parentheses getting in the way. Infix or prefix notation can be used as suits the occasion. Left-of-right expression evaluation also means that there is no ambiguity in any expression – from the compiler's perspective, even if not yours. Parentheses can always be used to group terms and override the default evaluation order but there will be far fewer once you abandon old (bad) habits.
Tip
Arrange your expressions with the goal of placing parentheses and brackets on the endangered species list. Most are just programming noise due to bad implementation choices unless you're doing Lisp.
4.1.3 The Gotcha of Right-to-Left Evaluation
Due to right-to-left evaluation, parentheses are needed to isolate the result of an expression that is the left operand of an infix operator. Let's take a closer look at this.
In any language you might have seen, the following expression evaluates to 10 but not in q. You can parenthesize or rearrange to get 10.
q)2*3+4
14
q)(2*3)+4
10
q)4+2*3
10
In some cases parentheses are simply unavoidable.
q)(2+3)*3+4
35
This is the one (and only) situation in which parentheses are required in q.
If the left operand of an operator is an expression it must be > parenthesized, otherwise the operator will bind to the right-most element of the expression.
Redundant parentheses
These parentheses on the left are often not needed in traditional programming and their omission in an overzealous extermination campaign is a common error for q newbies. Please don't overreact by putting parentheses around all operands "to be safe." Spend 5 seconds and think about it.
4.1.4 Rationale for No Operator Precedence
Operator precedence as normally encountered in programming languages is feeble. It requires all the components of an expression to be analyzed before anything can be evaluated. Moreover, it often results in the use of parentheses to override the very rules that are purportedly there to help.
Another common error is folks say that parentheses force things inside to be evaluated first. Nope. They indicate that the expression inside is to be evaluated independently of the rest of the enclosing expression. The compiler may choose to evaluate it when it sees fit.
Even more damning is that operator precedence forces complexity. Some programming languages (not q) allow user-written binary functions to be used infix. This would entail the extension of precedence levels to cover user functions, even those as yet unborn. If you've worked in such a language, you eventually run out of precedence levels (and patience) and end up needing parentheses with operators of the same precedence.
4.2 Match ~
The non-atomic binary Match operator ~ applies to any two q entities, returning the boolean result 1b if they are identical and 0b otherwise. For two entities to match, they must have the same shape, the same type and the same value(s), but they may occupy separate storage locations. Colloquially, clones are considered identical in q because they are indistinguishable in use.
This differs from the notion of identity in many traditional languages having pointers or objects. For example, in OO languages of C ancestry, objects are equal if and only if their underlying pointers address the same memory location. Identical twins are not equal. You might even have to write your own method to determine if one object is a deep copy of another.
There are no restrictions as to the type or shape of the two operands for Match. Try to predict each of the following results of match as you enter them into your console session.
q)42~40+2
1b
q)42~42h
0b
q)42f~42.0
1b
q)42~`42
0b
q)`42~"42"
0b
q)4 2~2 4
0b
q)42~(4 2;(1 0))
0b
q)(4 2)~(4;2*1)
1b
q)(())~enlist ()
0b
q)(1; 2 3 4)~(1; (2; 3; 4))
1b
q)(1 2;3 4)~(1;2 3 4)
0b
Tip
While learning or debugging q (except for the q gods who write perfect q code every time), applying Match can be an effective way to determine if you have what you intended. For example, q newbies often trip over the following, thinking the latter is a list.
q)42~(42)
1b
4.3 Equality and Relational Operators
It comes as a surprise to many who are new to vector programming that relational operators are atomic functions that return boolean values. Relational operations do not require the types of their operands to match, but they must be compatible.
4.3.1 Equality = and Inequality <>
The Equal operator = differs from Match ~ in that it is atomic in both operands. Thus it tests its operands atom-wise instead of in entirety. All atoms of numeric, temporal or char type are mutually compatible for equality, but symbols are compatible only with symbols.
Equal tests whether two compatible atoms represent the same value, without regard to type.
q)42=42i
1b
q)42=42.0
1b
q)42=0x42
0b
q)42="*"
1b
That last one may come as a surprise. It simply reflects that the underlying bit pattern of the ASCII char * is the same as the underlying bit pattern for the integer 42.
For temporal types the comparison is between the points on the calendar/clock rather than the underlying counts.
q)2000.01.01=2000.01.01D00:00:00.000000000
1b
q)2025.01.01<2025.02m
1b
q)12:00:00=12:00:00.000
1b
A symbol and a character are not compatible and an error results from the test,
q)`a="a"
'type
The Not Equal primitive is <>.
q)42<>0x42
1b
Note
The test Not Equal can also be expressed by applying not to the result of testing with =.
q)not 42=98.6
1b
When comparing floats, q uses multiplicative tolerance for non-zero values, which makes floating point arithmetic give reasonable results. At the time of this writing the tolerance is 10-14. See the KX web site for a detailed explanation of how q uses multiplicative tolerance.
q)r:1%3
q)\P 0
q)r
0.33333333333333331
q)1=r+r+r
1b
4.3.2 Not not
The unary, atomic operator not differs from its equivalent in some traditional languages. It returns a boolean result and has domain of all numeric, temporal and character types; it is not defined for symbols. The not operator generalizes the reversal of true and false bits to any entity having an underlying numeric value. It answers the Hamletonian question: to be, or not to be, zero.
The test against zero yields the expected results for boolean arguments.
q)not 0b
1b
q)not 1b
0b
The test against zero apples for any type with underlying numeric value.
q)not 0b
1b
q)not 1b
0b
q)not 42
0b
q)not 0
1b
q)not 0xff
0b
q)not 98.6
0b
For char values, not returns 0b except for the character representing the underlying value of 0.
q)not "*"
0b
q)not " "
0b
q)not "\000"
1b
For temporal values, an underlying 0 corresponds to the stroke of midnight at the millennium for types including a date and simply midnight for time-only types.
q)not 2000.01.01
1b
q)not 2024.01.01
0b
q)not 2000.01.01T00:00:00.000000000
1b
q)not 2000.01m
1b
q)not 00:00:00
1b
q)not 12:00:00.000000000
0b
4.3.3 Order: <, <=, >, >=
Less Than (<), Greater Than (>) At Most (<=) and At Least (>=) are atomic and are defined for all compatible atom types. Numeric and char types are mutually compatible, but symbols are only compatible with symbols. As with Equal, comparison for numeric and char types is based on underlying numeric value, independent of type.
q)4<42
1b
q)4h>0x2a
0b
q)-1.4142<99i
1b
As with Equal, the comparison for temporal types is between the points on the calendar/clock rather than the underlying counts.
q)2000.01.01<2000.01.01D00:00:00.000000001
1b
q)2025.01.01<2025.02m
1b
q)12:00:01>12:00:00.000
1b
For char atoms, comparing the underlying numeric value follows the ASCII collation sequence.
q)"A"<"Z"
1b
q)"a"<"Z"
0b
q)"A"<"O"
1b
q)"?"<"?"
0b
Tip
To see the entire ASCII collation sequence in compact form, do this. Enter this yourself to see it as it does not display well on this page.
q)16 16#"c"$til 256
For symbols, comparison is based on lexicographic order.
q)`a<`b
1b
q)`abc<`aba
0b
Now that we are familiar with relational operations on atoms, let's examine their item-wise extensions to simple lists. Notice the simple boolean list returned.
q)2 1 3=1 2 3
001b
q)10 20 30<=30 20 10
110b
q)2=1 2 3
010b
q)"zaphod"="Arthur"
000100b
q)`a`b`a`d=`a`d`a`b
1010b
4.4 Basic Arithmetic: +, -, *, %
The arithmetic operators are atomic and come in binary and unary flavors. We begin with the four operations of elementary arithmetic. Arithmetic operations are defined for all numeric and temporal types, and all numeric types are compatible.
| Symbol | Name | Example |
|---|---|---|
| + | Add | 42+67 |
| - | Subtract | 42.0-5.3456 |
| * | Multiply | 2h*3h |
| % | Divide | 42%6 |
Arithmetic looks pretty much like other programming languages, except that division is represented by (%) since '/' is used to delimit comments. You'll just have to get used to this.
q)2+3
5
q)a:6
q)b:7
q)b-a
1
q)a*b
42
q)4%2
2f
Tip
The result of division is always a float.
The major difference in q arithmetic expressions is due to right-to-left evaluation and the absence of precedence.
q)6*3+4
42
Type promotion for arithmetic operators follows two rules:
-
Binary types are promoted to int
-
The result type of an operation is the narrowest type that will accommodate both operands.
Here are examples of binary data promotion. Note that arithmetic on booleans is not performed modulo 2.
q)1b+1b
2i
q)42*1b
42
q)5i*0x2a
210i
Important
Overflow and underflow are not trapped on arithmetic operations on integer types.
q)9223372036854775806+4
-9223372036854775806
q)[2](http://www.apple.com)*5223372036854775800
-8000000000000000016
q)-9223372036854775806-4
9223372036854775806
When even one floating-point type occurs in an expression, the result is a float.
q)6+7.0
13f
q)1.0+1b
2f
q)6.0*7.0e
42f
Tip
The symbols for arithmetic operators are always binary in q. In particular, while '-' is used as a lexical marker to denote a negative number, there is no unary function (-) to negate a numeric value. Its attempted use for such generates an error. Use the operator (neg) instead.
q)42
42
q)-42
-42
q)a:42
q)-a / error
'-
q)neg a
-42
Being atomic, arithmetic operators and their type promotion are performed atom-wise on lists.
q)1.0+10 20 30
11 21 31f
q)10 20 30%1 2 3
10 10 10f
q)100 200 300+1b
101 201 301
q)1+(100 200;1000 2000)
101 201
1001 2001
4.5 Greater | and Lesser &
These atomic binary operators follow the same type promotion and compatibility rules as arithmetic operators. They are defined for all values with underlying numeric values but are not defined for symbols and GUIDs.
The Greater operator | returns the larger of its operands; this reduces to logical "or" for binary operands. The Lesser operator & returns the smaller of its operands, which reduces to logical "and" for binary operands.
q)42|43
43
q)98.6&101.9
98.6
q)0b|1b
1b
q)1b&0b
0b
q)42|0x2b
43
q)"a"|"z"
"z"
q`a|`z / error
'type
Being atomic they operate atom-wise on lists.
q)2|0 1 2 3 4
2 2 2 3 4
q)11010101b&01100101b
01000101b
q)"zaphod"|"arthur"
"zrthur"
For readability of logical operations on binary data, (|) can also be written as (or) while (&) can be written as (and).
q)1b or 0b
1b
q)1b and 0b
0b
q)42 or 43
43
4.6 Amend :
An overload of : that is "assign in place."
4.6.1 Amend in C Language
We are familiar with the basic form of assignment.
q)a:42
Programmers from languages with C heritage are familiar with expressions such as,
x += 2; // C expression that assigns in place
This has the same effect as the following but can be more efficiently implemented at the machine instruction level.
x = x + 2; // C expression
Reading the first statement succinctly as "add 2 to x in place" motivates the interpretation of the operation as "amend." To wit, the value assigned to x is amended by applying the operation + with the supplied operand 2.
4.6.2 Simple q Amend
Transliterating the above C expression to q yields the +: operation to amend a variable by adding in place.
q)x:42
q)x+:1
q)x
43
There is nothing special about + here. Amend can be used with any binary symbolic operator having compatible type.
q)a:43
q)a-:1
q)a
42
q)a&:21
q)a
21
Tip
In spite of the linguistic dissonance, a q variable can be amended even if it has not been previously assigned. In a fresh q session:
q)x
'x
q)x+:42
q)x
42
4.6.3 Amend with Lists
The capability to modify in place extends to lists and indexing.
q)L:100 200 300 400
q)L[1]+:99
q)L
100 299 300 400
q)L[1 3]-:1
q)L
100 298 300 399
q)L1:(1 2 3; 10 20 30)
q)L1[;2]+:100
q)L1
1 2 103
10 20 130
A very useful idiom is ,: which appends to a list in place.
q)L:1 2 3
q)L,:4
q)L
1 2 3 4
q)L,:100 200
q)L
1 2 3 4 100 200
Tip
Amend does type promotion based on the operator it is combined with, except for ,: which requires exact type match when used with a simple list. This is good thing as it ensures that table columns maintain their types.
q)L:1.1 2 2 3.3
q)L[1]+:100
q)L,:100
'type
4.7 Exponential Primitives: sqrt, exp, log, xexp, xlog
4.7.1 sqrt
The atomic unary sqrt has domain all numeric values and returns a float representing the square root of its input. It returns null when the square root is not defined.
q)sqrt 2
1.414214
q)sqrt 42.4
6.511528
q)sqrt 1b
1f
q)sqrt -2
0n
4.7.2 exp
The atomic unary exp has domain all numeric values and returns a float representing the base e raised to the power of its input.
q)exp 1
2.718282
q)exp 4.2
66.68633
q)exp -12i
6.144212e-06
Tip
Do not confuse the e used in the display of base-10 scientific notation with the mathematical base of exponentials and natural logarithms.
q)1e10 / this is ten billion
1e+10
4.7.3 log
The atomic unary log has domain all numeric values and returns a float representing the natural logarithm of its input. It returns null when the logarithm is not defined.
q)log 1
0f
q)log 42.0
3.73767
q)log .0001
-9.21034
q)log -1
0n
4.7.4 xexp
The atomic unary xexp has domain all numeric values in both operands and returns a float representing the left operand raised to the power of the right operand. When the mathematical operation is undefined, the result is null.
q)2 xexp 5
32f
q)-2 xexp .5
0n
4.7.5 xlog
The atomic unary xlog has as domain all numeric values in both operands and returns a float representing the logarithm of the right operand with respect to the base of the left operand. When the mathematical operation is undefined the result is null.
q)2 xlog 32
5f
q)2 xlog -1
0n
4.8 More Numeric Primitives
4.8.1 Integer Division div and Modulus mod
The atomic binary div is atomic in both operands, which are numeric values. The result is the integer quotient of the left operand (dividend) by the (positive) right operand (divisor), which is equal to the result of normal division rounded down to the next lower integer. The operation returns null for zero divisor.
q)7 div 2
3
q)7 div 2.5
2
q)-7 div 2
-4
q)-7 div 2.5
-3
q)7 div -2
-4
q)3 4 5 div 2
1 2 2
q)7 div 2 3 4
3 2 1
q)3 4 5 div 2 3 4
1 1 1
The binary mod is atomic in both operands, which are numeric values. The result is the remainder of the integer quotient of the left operand (dividend) by the positive right operand (divisor). It is equal to
dividend -- (dividend div divisor)
The result is null for zero divisor.
q)7 mod 2
1
q)7 mod 2.5
2f
q)-7 mod 2
1
q)-7 mod 2.5
0.5
q)7 mod -2
-1
q)3 4 5 mod 2
1 0 1
q)7 mod 2 3 4
1 1 3
q)3 4 5 mod 2 3 4
1 1 3
Tip
Many languages use % for modulus, but it is division in q. This is a common mistake of qbies.
4.8.2 Sign signum
The atomic unary signum has domain all numeric and temporal types and returns an int representing the sign of its input, where 1i represents positive, -1i represents negative and 0i represents a zero.
q)signum 42
1i
q)signum -42.0
-1i
q)signum 1b
1i
q)signum 0
0i
Temporal types are treated as their underlying offsets.
q)signum 1999.12.31
-1i
q)signum 12:00:00.000000000
1i
4.8.3 Reciprocal
The atomic unary reciprocal has domain all numeric types and returns the float result of 1.0 divided by the input. It returns the appropriately signed infinity for the reciprocal of signed 0. It is faster than 1%x.
q)reciprocal 0.02380952
42.00001
q)reciprocal 0.0
0w
q)reciprocal -0.0
-0w
4.8.4 Floor and Ceiling
The atomic unary floor has domain integer and floating point types and returns a long representing the largest integer that is less than or equal to its argument.
q)floor 4.2
4
q)floor 4
4
q)floor -4.2
-5
Tip
The floor operator can be used to truncate or round floating point values to a specific number of digits to the right of the decimal.
q)x:4.242
q)0.01*floor 100*x
4.24
Analogous to floor, the atomic unary ceiling has domain numeric types and returns the smallest long that is greater than or equal to its argument.
q)ceiling 4.2
5
q)ceiling 4
4
q)ceiling -4.2
-4
Note
In previous versions of q, floor and ceiling did not apply to short types. Now they do.
q)floor 4h
4h
4.8.5 Absolute Value abs
The atomic unary abs has domain all integer and floating-point types. The result is the input when it is greater than or equal to zero and its negation otherwise. The result of abs has the same type as the argument except for binary types, which are type promoted to int.
q)abs 42
42
q)abs -42
42
q)abs 1b
1i
4.9 Operations on Temporal Values
Because all q time values are integral offsets from canonical base points, properties and operations on temporal types are simple. For example, one value of a temporal type is before another value of the same type just when the same is true of their underlying integer values. When dealing with temporal values of different types, q implicitly promotes to the wider type and then proceeds as just described.
Moreover, basic temporal arithmetic is integer arithmetic. The difference of two (absolute) temporal values of the same type is the span given by the difference of their underlying integer counts. Conversely, given a temporal value, adding an integer to it (i.e., to its underlying integer count) yields a temporal value of the same type.
Tip
Casting reveals the underlying integer count of any temporal value.
q)`int$1999.12.31
-1i
q)`int$2013.01m
156i
q)`int$12:00:00.123
43200120i
q)`long$12:00:00.123456789
43200123456789
Note
There is no concept of time zone in q temporal values. Those of us who wrestled with Java's early time implementation are thankful.
4.9.1 Temporal Comparison
Comparison within a temporal type amounts to simple comparison of the underlying integral offsets. Comparison across temporal types recognizes that the underlying values express different units and converts them to common units.
For example, midnight on the second day of the millennium should be equal to the second day, but a naïve comparison of the underlying counts will not tell us.
q)2000.01.02=2000.01.02D00:00:00.000000000
1i
q)`int$2000.01.02
1i
q)`long$2000.01.02D02:00:00.000000000
93600000000000
Values of different types should be compared in the same units, which effectively amounts to converting to the most granular units. The cast operator has the logic for such conversions built-in.
q)`timestamp$2001.01.02
2001.01.02D00:00:00.000000000
To compare temporal values of different types, q converts to the most granular type and then does a straight comparison of the underlying values.
q)2000.01.01<2000.01.01D12:00:00.000000000
1b
4.9.2 Temporal Arithmetic
In contrast to many traditional languages, expressions involving temporal types and numerical types that should make sense actually work as expected. For example, temporal values are their underlying offsets for equality and comparison testing against numeric values.
q)2000.01.01=0
1b
q)12:00:00=12*60*60
1b
q)1999.12.31<0
1b
Adding an integral value to a temporal value works because it is just added to the underlying offset.
q)2024.12.31+1
2025.01.01
q)2025.01.01+til 31
2025.01.01 2025.01.02 2025.01.03 2025.01.04 2025.01.05 ...
q)12:00:00+1
12:00:01
Adding a temporal value to another causes it to be viewed as span, as you should want.
q)12:00:00+01:00:00
13:00:00
One important case is adding a timespan to a date to yield a timestamp. There is actually some calculation under the covers to make this work.
q)2025.01.01+12:00:00.000000000
2025.01.01D12:00:00.000000000
The difference between two values of a temporal type that counts temporal units is the int difference of their underlying counts.
q)2001.01.01-2000.01.01 / leap year
366i
q)2025.06m-2025.01m
5i
The difference between two values of a type with time component is the difference of the underlying offsets, expressed as the same type - i.e., as a span.
q)2025.01.01D00:00:00.000000000-2024.01.01D00:00:00.000000000
365D00:00:00.000000000
q)12:00:00-11:00:00
01:00:00
q)12:00-11:00
01:00
4.10 Operations on Infinities and Nulls
Here we summarize the various behaviors of nulls and infinities in one place.
The float infinities and nulls act in the mathematically correct fashion in numeric expressions and comparisons. Integer infinities act correctly in comparisons and act as their underlying (finite) values in other operations.
The bit patterns of the integral nulls and infinities are legitimate base-2 integral representations with the high-order bit being the sign.
| Value | Bit Representation |
|---|---|
| 0Wh | 0111111111111111b |
| -0Wh | 1000000000000001b |
| 0Wi | 01111111111111111111111111111111b |
| -0Wi | 10000000000000000000000000000001b |
| 0W | 0111111111111111111111111111111111111111111111111111111111111111b |
| -0W | 1000000000000000000000000000000000000000000000000000000000000001b |
The same type promotion rules apply to a null as for a normal value of that type.
An infinity value equals or matches only itself. All nulls are equal (they represent missing data), but different type nulls do not match (type matters).
In contrast to some languages, such as C, separate instances of NaN are equal.
q)(0%0)=0%0
1b
The not operator returns 0b for all infinities and nulls since they all fail the test of equality with 0.
q)not 0W
0b
q)not -0w
0b
q)not 0N
0b
The neg operator reverses the sign of infinities but does nothing to nulls since sign is meaningless for missing data.
q)neg 0W
-0W
q)neg -0w
0w
q)neg 0N
0N
We saw previously that for any numeric type
null < negative infinity < normal value < positive infinity
Nulls of different type, while equal, are not otherwise comparable - i.e., any relational comparison results in 0b.
Infinities of different type are ordered by their width. For positive infinities
short < int < long < real < float
For negative infinities
-float < -real < -long < -int < -short
Some examples follow. Try to predict the result before looking at the result.
q)42<0W
1b
q)-0w<42.0
1b
q)-0w<1901.01.01
1b
q)-0w<0w
1b
q)0W<0w
1b
q)-0w<0W
1b
q)-10000000<0N
0b
q)0N<42i
1b
q)0n<-0w
1b
The null symbol is less than any other symbol
q)`a<`
0b
The behavior of | and & with infinities and nulls follows from the rules for equality, comparison and type promotion mentioned already.
q)42|0W
0W
q)-42&0N
-42
q)0w|0n
0w
q)-0w&0n
0n
q)0n|0N
0n
q)0Wi&0W
2147483647
The last result obtains because int infinity is promoted to a long and its bit pattern corresponds to the maximal positive 32-but integer.
4.11 Views :: (Advanced)
Because q is a strict language, expressions are normally evaluated as soon as encountered by the interpreter. In particular, assignment with an expression on the right requires the expression to be evaluated before the result is assigned.
A view (in earlier versions of the text alias) is a variable that is an expression – i.e., it is not the result of expression evaluation but the expression itself. Otherwise put, a view provides a way to defer evaluation (reduction) of an expression.
Evaluation of a view is lazy, meaning that it occurs only when necessary. More precisely, evaluation is forced when the variable is referenced, at which point a determination is made whether the expression needs to be (re)evaluated.
-
If it is the first reference or if any variable in its associated expression has changed since the last evaluation, evaluation proceeds with the current values of all the variables in the expression. The result of the most recent evaluation is then stored internally and also returned. The stored result is said to be memoized.
-
If no variables in the expression have changed since the previous evaluation, the memoized value is returned.
The view variable is said to depend on any variables in its expression.
4.11.1 Creating a View with Double Colon
Double colon :: used outside a function body defines the variable to its left as an alias of the expression to its right. This variable is called a view. When the aliased variable is referenced, the underlying expression is (re)evaluated as described above. The following trivial example defines b as a view on a, contrasted with c which is just assigned the value of a. Observe that the subsequently changed value of a is reflected in b but not in c
q)a:42
q)b::a
q)c:a
q)a:43
q)b
43
q)c
42
Here is a more interesting view.
q)w::(x*x)+y*y
q)x:3
q)y:4
q)w
25
q)y:5
q)w
34
Tip
For newbies here, the mysteriously named utility 0N! is the identify function with the side effect of displaying its input on the console. It is a non-invasive way to inspect the inner workings of an in-flight evaluation.
q)w::(0N!x*x)+y*y
q)x:3
q)y:4
q)w
9
25
q)w
25
q)y:6
q)w
9
45
Observe that the first time w is referenced, the expression is evaluated, as indicated by the display of the value of x*x. On the next reference, the expression is not re-evaluated since none of the variables it depends on have changed. After changing y, the last reference causes re-evaluation.
More generally a view can be defined with expressions sequenced by the operator ;. Remember that the operation a;b evaluates a and discards its value then evaluates b and returns its value. The value of the expression after the final ; is returned when the view is referenced. In the following example a and b are identified as globals that are part of the dependency for the returned select.
q)v::a;b;select from t where a in b
4.11.2 View vs. Function
A function also represents a form of deferred evaluation. In the example of the previous section, we can also define
q)fu:{(x*x)+y*y}
q)fu[3;4]
25
There are two key differences between a view and the analogous function.
-
To evaluate an expression wrapped in a function you explicitly provide the arguments and apply the function all in one step. With a view you set the variables at any point and the expression is evaluated when, and only when, the alias variable is referenced.
-
The function does not memoize its result, so it recalculates on every application, even if the arguments have not changed since the previous application.
4.11.3 Dependencies
A view depends on the entities in its associated expression. In our previous example w depends on x and y. A list of all dependencies is maintained in the system dictionary .z.b. Assuming we start in a fresh q session,
q)w::(x*x)+y*y
q).z.b
x| w
y| w
Each key in .z.b is associated to all the view entities that directly (immediately) depend on the variable with that name.
You can create a view with another view in its expression. This results in a chain of dependencies. The entire chain is resolved lazily only upon reference.
q)u::w*w
q).z.b
x| w
y| w
w| u
q)x:3
q)y:4
q)u
625
Such a recursive definition leads to a hierarchy of dependencies, in which a view depends not only directly on the variables in its own expression, but also indirectly on any variables that its expression depends on, etc. You can easily build sophisticated dependency chains this way.
Tip
You can easily build unmaintainable code this way. If you want to see the dependency graph you will have to build the display yourself. This is not difficult but it isn't trivial for complex dependencies.
As of q 3.2, a view can refer to itself in its expression. When referenced it will use its last value. You must take care not to reference it, either internally or externally, until it has been calculated at least once.
q)v::$[b;1;v+1]
q)v
'b
[1] v::$[b;1;v+1]
^
q))\
q)b:0
q)v
'type
[1] v::$[b;1;v+1]
^
q))\
q)b:1
q)v
1
q)b:0
q)v
2
As of 3.2, references that would create an infinite loop are detected on reference, not at view creation. Continuing our above example, this redefinition would create an infinite loop.
q)v::$[b;1;v+1]
q)v
'type
[1] v::$[b;1;v+1]
^
You can list the current views using the built-in function views. In a fresh q session,
q)c::a+b
q)d::c+a
q)views `
`s#`c`d
q)\b
`s#`c`d
You can list the views that require (re)calculation, called pending views, with \B. Continuing our example, both c and d are initially yet to be calculated. After assigning values to a and b this is still the case. Once c is referenced only d is pending.
q)\B
`s#`c`d
q)c
85
q)\B
,`d
You can see the definition of a view as text by applying the built-in function view to its symbolic name. You can also ask the root namespace to disclose the actual view expression.
q)view `c
"a+b"
q)`. `c
a+b
Finally you can fully disclose all the information of a view by applying value to its entry in the root namespace. In a fresh q session,
q)c::a+b
q)value `. `c
::
`s#(+;`a;`b;`s#(?;`t;,`s#,`s#(in;`a;`b);0b;()))
`a`b`t
(("q";`);"";0;"c::a+b")
Now assign values to the dependencies a and b and then reference c. The view information now includes the memoized last value of c.
q)(a; b):(42; 43)
q)c
85
q)value `. `c
85
`s#(+;`a;`b)
`a`b
(("q";`);"";0;"c::a+b")
4.11.4 Table Views
Aliasing is commonly used to provide something akin to a SQL view by specifying an expression involving a table – e.g., a qSQL query. In a fresh q session,
q)t:([]c1:`a`b`c`a;c2:20 15 10 20;c3:99.5 99.45 99.42 99.4)
q)v::select sym:c1,px:c3 from t where c1=`a
q)v
sym px
--------
a 99.5
a 99.4
q)update c3:42.0 from `t where c1=`a
`t
q)v
sym px
------
a 42
a 42
The table dependencies of a view are reflected in .z.b.
q).z.b
t| v
(("q";`);"";0;"c::a+b")
As pointed out in the introduction to this section, if you need to capture globals in a select, include them in the view. Here you can see in the blue characters that a and b are dependencies and not column names.
q)v::a;b;select from t where a in b
q)value`. `v
::
`s#(";";`a;`b;`s#(?;`t;,`s#,`s#(in;`a;`b);0b;()))
`a`b`t
(("q";`);"";0;"v::a;b;select from t where a in b")
4.11.5 Notes on Views
Views are a very powerful tool available to the q programmer. We record here some cautions and limitations about their use.
First, a view is a form of mutable global state in which a single global assignment to one of its global variable dependencies can ripple through a possibly extremely complex chain of dependencies. If you intend to build sophisticated chains, you should build tools to display and reason about the dependencies.
Second, views and their dependencies can be defined only in the default namespace. Otherwise put, the space of views is flat and all views live it the root.
Third, views can only be created from a console session or in a script as it is loaded. Inside a function the glyph :: means to amend the global variable to its left.
You might think you could apply parse to the expression that creates a view and then eval to the resulting parse tree. This does not work because aliases/views are not handled by the normal parse/eval mechanism in q. In a fresh q session,
q)pt:parse "b::a"
q)pt / this looks ok
::
`b
`a
q)a:42
q)eval pt / we mean to create the view
42
q)b / b exists
42
q)a:43
q)b / no view created
42
The preceding discussion means that there is no syntactic form to create an alias/view programmatically. To do so you must resort to invoking the full interpreter in value.
q)createView:{[vname; vexpr] value vname,"::",vexpr}\
q)createView["b"; "2*a"]
q).z.b
a| b
q)a:42
q)b
84
Whenever you invoke the interpreter, your spidey sense should be warning you that you are on the fringes of q practice.
Finally, a view should never have side effects. If you create a sophisticated dependency chain that has side effects, when the dependency chain updates changes will ripple through the global context in ways that will be virtually impossible to trace.