Skip to content

Iterator syntax

DRAFT

Implicit iteration is (almost always) the best iteration in q. For everything else, there are iterators.

Iterators are… amazing. To use them fluently, you need a firm grip on their syntax.

For simple cases, q’s iterators are easier to learn than k’s adverbs. But they introduced some syntactic complexity, which we must now master.

Iterators are unary operators with postfix syntax. That is to say, an iterator has a single argument, which can be written on its left. It returns (or derives) a function, the derived function.

A simple example:

q)("quick";"brown";"fox")?'"ioo"
2 2 1

Above, the derived function Find Each ?' finds

  • "i" in "quick"
  • "o" in "brown"
  • "o" in "fox"

The Each iterator ' takes Find ? as its argument, to derive the function Find Each ?'. This is a thing. It has type 106 and you can Assign it a name.

q)fe: ?'          / Find Each
q)type fe
106h
q)fe[("quick";"brown";"fox");"ioo"]
2 2 1

Postfix syntax is conventional, and good q style. However, bracket syntax always works.

q)'[?] [("quick";"brown";"fox");"ioo"]
2 2 1
q)fe ~ ('[?])
1b

To iterate a function that is the result of evaluating an expression, Apply At might be what you need.

q)@[';?] [("quick";"brown";"fox");"ioo"]
2 2 1
q)fe ~ @[';?]
1b

Variadic derived functions

Functions derived from binary functions by the Each Prior, Over and Scan iterators are variadic.

q)+':[1 2 3 4]           / Add Each Prior (unary)
1 3 5 7
q)+':[1000;1 2 3 4]      / Add Each Prior (binary)
1001 3 5 7
q){x+y}':[1 2 3 4]       / {lambda} Each Prior (unary)
0N 3 5 7
q){x+y}':[1000;1 2 3 4]  / {lambda} Each Prior (binary)
1001 3 5 7

q)+\[1 2 3 4]            / Add Scan (unary)
1 3 6 10
q)+\[1000;1 2 3 4]       / Add Scan (binary)
1001 1003 1006 1010
The binary and unary applications are closely related.

Each Prior

For r:x f':y the value of r[0] is f[y 0;x]. The value of x is used instead of y[-1].

See Variations on Each for its value when r:f':[y].

Accumulators Over and Scan
For x f/y and x f\y the result of the first iteration is f[x;y 0].
For f/[y] and f\[y] the result of the first iteration is y[0].
The value of x is used as a starting point for the accumulation.

Derived functions are overloaded

As the q Reference tells us, symbols such as ? are overloaded. The ? symbol denotes several operators, such as Find, Roll, Select and Vector Conditional.

So which of them does ?' denote?

All of them.

The derived function is as variadic as the argument function.

Many binary operators are secretly variadic

In k many binary operators, such as + and &, have unary overloads, which in q are wrapped as keywords. For example, the unary forms of + and # are, respectively, flip and count.

These unary overloads are exposed infrastructure from k, which is undocumented and unsupported. But there all the same.

As a consequence, a derived function such as #', which looks like it should be unambiguously binary, is actually variadic. It is both Take Each and Count Each, and has both binary and unary syntax.

And count', which looks unambiguously unary, has variadic syntax too.

All functions derived postfix have infix syntax

That’s right, all of them.

Regardless of their actual ranks

Some of them have ranks to match.

q)+/[1000;2 3 4]   / +/ as binary, bracket syntax
1009
q)1000+/2 3 4      / +/ as binary, infix syntax
1009

Some don’t, e.g. count'.

So how is q to know whether, say, +/ is to be parsed as a binary or a unary?

You tell it.

You have four ways to do that:

  1. Bracket syntax, as above, is unambiguous: e.g. +/[x;y] (binary), or +/[x] (unary).

  2. Infix syntax is unambiguous, e.g. x+/y (binary).

  3. Name it, and apply it with bracket syntax (unary or binary) or prefix syntax (unary).

  4. Parenthesise it, and apply it with prefix syntax (unary).

Project a binary function

To project a binary derived function, include the semicolon. That is, +/[1000;] is a projection, but +/[2 3 4] is not.

Functions derived with bracket syntax do not have infix syntax

For example, +\ is variadic and has infix syntax.

q)100+\1 2 3            / binary, infix
101 103 106      
q)+\[100;1 2 3]         / binary, bracket
101 103 106      
q)+\[1 2 3]             / unary, bracket
1 3 6
However, (\[+]) – also variadic – does not have infix syntax.
q).[~] parse each ("+\\";"(\\[+])")
1b
q)(\[+]) [100;1 2 3]   / binary, bracket
101 103 106
q)(\[+]) [1 2 3]       / unary, bracket
1 3 6
q)(\[+])  1 2 3        / unary, prefix
1 3 6
q)100(\[+]) 1 2 3      / binary, infix ERROR
'Cannot write to handle 100. OS reports: Bad file descriptor
  [0]  100(\[+]) 1 2 3
       ^
It is hard to think of a use case for applying an iterator with bracket syntax.

Naming

Naming a derived function removes its infix syntax.

q)tot: +/
q)tot 2 3 4
9
q)tot[100;2 3 4]
109

You can evaluate 100+/2 3 4 but not 100 tot 2 3 4 because, while tot is variadic, as +/ is, it does not inherit its infix syntax.

But because tot has lost infix syntax, you can apply it as a unary without ambiguity. That is, you do not have to use parentheses, Apply At, or bracket syntax; you can just write tot 2 3 4.

You can write a lot of q without mastering this, because q provides keywords for the most common derived functions, and also for the iterators themselves.

Parenthesising

Parenthesising a derived function gives it noun syntax.

Now, in the expression it appears in, it’s a thing. It doesn’t have agency. It doesn’t act on anything else. Unless… you apply it.

q)(+/) . (1000;2 3 4)    / Apply (binary)
1009
q)(+/) @ 2 3 4           / Apply At (unary)
9

As usual, good q style prefers infix and prefix syntax.

q)1000+/2 3 4
1009
q)(+/) 2 3 4
9

Now you understand why derived functions are parenthesised for unary application.

Keywords for unary application of derived functions

ƒ    ƒ/    ƒ\
--------------
+    sum   sums
*    prd   prds
|    max   maxs
     any
&    min   mins
     all

These are not learning aids to be abandoned once you have mastered iterator syntax: good q style prefers the keywords.

Keywords for iterators

Similarly, q has keywords for the iterators themselves.

'   each
/   over
\   scan
':  prior

These keywords are not themselves iterators. They are binary functions with infix syntax.

q)type(')       / iterator
103h
q)type(each)    / function
100h

The each keyword takes as its left argument a unary function: x each y is equivalent to x'[y] or (x')y.

As functions, these keywords do not return derived functions. But you can project them and apply the projection much as you would apply a derived function.

q)ced: count'            / count Each (derived function)
q)cep: count each        / count Each (projection: each[count;])
q)ced ("quick";"brown")
5 5
q)cep ("quick";"brown")
5 5

Again, good q style prefers count each x to count'[x] or (count')x. But this has its limits.

q)M:(("quick";"brown");("fox";"jumps"))
q)count''[M]
5 5
3 5

Above, the derived function count' is the argument of the second Each, deriving the function count''.

q)ce0: count''    / count each each
q)ce1: (count')'  / postfix syntax: count' is the argument of '
q)ce0 ~ ce1
1b

The two iterators amount to one loop nested inside another. Using the each keyword would not make this clearer.

q)(count each)each M
5 5
3 5
q)each[count each]M
5 5
3 5

Use keywords for simple expressions; for more complex expressions, master iterator syntax.

Do this for all the iterator keywords: each, over, scan and prior.

Applicable values

The argument of an iterator is an applicable value.

That includes functions, but also lists, dictionaries, and communication handles.

Finite-state machines

You can represent a finite-state machine as a vector in which items are also indices, or a dictionary in which values are keys.1

q)show fsm:-10?10    / finite-state machine
4 3 9 1 5 6 8 0 7 2
q)fsm\[7]
7 0 4 5 6 8

q)show rps:`rock`paper`scissors!`scissors`rock`paper
rock    | scissors
paper   | rock
scissors| paper
q)1 rps\`rock`paper
rock     paper
scissors rock

A data atom cannot be applied or indexed. But an int atom that is also a communication handle can be applied and iterated.

q)(-1')("quick";"brown";"fox");
quick
brown
fox

Two kinds of iterators

Functions derived by

map iterators
work independently on the items of their argument/s
accumulator iterators
use the result of each iteration as an argument value for the next iteration.

The map iterators are Case and variations of Each '.

The accumulators are forms of Over / and Scan \.


Exercises

FIXME


  1. Thanks to Oleg Finkelshteyn for this insight.