Skip to content

12. Workspace Organization

12.0 Overview

The collection of all q entities that have been created in a q session comprises the workspace. This includes not only explicitly created variables but also ancillary items such as enumerations, open handlers, tables mapped from storage and other internal structures.

Like any programming environment of reasonable complexity, q has the potential for name clashes – e.g., when two different scripts define variables of the same name. Because q does not have lexical scoping and local variables are strictly scoped to the function in which they are defined, name clashes between locals is not a problem. But global variables are exposed. To illustrate, suppose you load a script that creates a global foobar. Then you load another script that also creates foobar. The result is that the second script overwrites the value assigned in the first script.

This is the simplest example of a condition in which two portions of a program contend for the same resource. Things get more complicated when concurrency is introduced, in which case the contention can lead to what is called a race condition. There is a school of programming that says mutable shared state is the root of all evil in distributed programming. Fortunately q is single-threaded by default and the implementation of threads (wisely) does not support mutating global variables. Nonetheless, simple name clashes are still a real problem.

12.1 Namespaces

A partial solution to name clashes is namespaces, which is the idea of placing a (usually hierarchical) structure on names. This is customarily done by specifying a separator character, which is distinct from other valid name characters. You are familiar with this construct from the file system used by most operating systems. The Unix file system uses the separator / and the Windows file system uses \.

The idea is that a compound name with embedded separators represents a nested series of containers. For example, in the Unix file system, the path /mydir/myfile represents a directory (container) mydir that holds a file myfile. Or /d1/d2/fn represents a directory d1 that contains a directory d2 that contains the file fn. The root container, designated / in Unix, holds all other entities.

Namespaces in many programming languages work similarly, using . as the separator. In q the containers are called contexts. For example, .jab.x represents a variable x contained in the jab context. The context jab has the symbolic name `.jab and the fully qualified name of its variable is `.jab.x.

A notable difference between q namespacing and most others is the way the root is handled. Naming for entities in the root context does not work as you might expect based on the file system analogy. The syntactic form .name does not refer to the global variable name in the root context; rather, it refers to the context name in the root. The fully qualified name for an entity x in the root is simply x.

This has the following consequence that causes grief to qbies.

Namespaces Fact of Life #1

There is no syntactic way inside a function to distinguish between a local variable x and the global x in the root namespace.

If you follow the Namespacing conventions at the end of this chapter, you can minimize potential discomfort. However, based on real-world experience, we point out the following.

Tip

Inside a function you can not access the global variable foo in the root context as .foo. Instead, .foo refers to the dictionary that holds all entities of the foo context. If you assign a value to .foo you wipe out the entire foo context.

12.2 Contexts

Namespacing in q is implemented with dictionaries. A context is a dictionary whose keys are the symbolic names of the variables in the corresponding namespace. The context dictionary associates each variable name with its currently assigned value.

The name of a context has the same restrictions as q variable names except that . is allowable. The following are all valid names of contexts in the root.

.a
.q
.cat
.an_ugly_context_name

Thus .cat.bubba represents the entity bubba in the .cat context.

With that out of the way, we can nest contexts.

.cat.bengal.nuba
.cat.abyssinian.orion

Here we have the entity nuba in the bengal context in the cat context; or the orion entity in the abyssinian context in the cat context.

Astute readers already have a question. How do we distinguish between the first case, in which .cat.bubba is the variable bubba in the cat context, and the second case where .cat.bengal is a context? For example, can we emulate the file system, which appends the separator / to indicate a directory (container). The answer is:

Namespacing Fact of Life #2

The interpretation of a fully qualified name depends on its underlying value. If it is a properly constructed dictionary, it is a context. If it is anything else, it is a variable.

KX reserves all root namespaces of a single letter, as well .kx, for its own use. It is worth noting that most q built-in functions that are not written in C live in the .q namespace. As of this writing (Sep 2015) the namespaces that are actively used by KX include .h, .j, .o, .q, .u, .z and .Q.

Tip

Although q prohibits you from re-assigning its own functions during program execution, some programmers think it is clever to override their definitions in the q.k file. This is bad practice bordering on malfeasance. It courts disaster should KX change the internal implementation of those routines in a future release.

12.3 Creating Contexts

When you start a fresh q session, all global variables you create live in the root context. All previous examples in this tutorial have created global variable variables in the root simply because we did not indicate otherwise – i.e., we used only unqualified variable names.

Start a fresh q session and create some global variables, beginning in the root.

q)answer:42
q)calculate:{(x*x)+(-1*x)+1}

To use namespacing, there is no equivalent to mkdir that creates a context before it can be used. Simply specify a qualified name and the context will be created if it does not exist.

q).foo.a:42 / context foo created
q).foo.double:{2*x} / existing context foo used

Now suppose a separate script uses the bar namespace with different definitions for a and double.

q).bar.a:43 / context bar created
q).bar.double:{2*x} / existing context bar used

We can now load that script without clobbering our root variables, so we have solved the original problem posed in the introduction to this chapter.

Be very careful about placing globals in the root. As the author’s father would say, “You’re cruisin’ for a bruisin’”.

When working with contexts, apply an overload of the key operator to list the names of all contexts in the root.

q)key `
`q`Q`h`j`o`foo`bar

There is no need to stop at one level of namespace. You can nest as deeply as you like and all the intermediate contexts will be created as needed. Suppose we work for a company Adiabatic Programming Laboratory. We might structure our library of computation routines something like the following.

q).apl.core.calc.array.fill0:{0.0^x}

This single assignment causes the nested series of contexts apl, core, calc and array to be created and a function fill0 to be placed in the innermost context array.

12.4 Context as Dictionary

As mentioned previously, a context is a specially formatted q dictionary. Its keys are the symbolic names of the variables it contains; each is associated with the current value of the corresponding variable.

The context dictionaries are dictionaries that live in the workspace along with ordinary variables, which have special meaning to q. We can perform operations on them just as with our own dictionaries.

When we start a fresh q session, the root dictionary is empty. As with any variable, we can reveal the root context by applying get (or value) to its name and then applying the utility .Q.s1 to display its internal form since it is empty.

q).Q.s1  get `.
"(`symbol$())!()"

Here we see an empty key list of symbols and a general empty list of values. After defining variables in the root, an ordinary dictionary emerges.

q)get `.
a     | 42
double| {2*x}

The entries in a context dictionary are sorted by name.

Things get more interesting when we use namespacing and create a context.

q).jab.wrong:43
q)get `.
a     | 42
double| {2*x}
q)get `.jab
      | ::
wrong | 43

Observe that the newly created context dictionary for .jab is not physically in the root directory; it is a separate dictionary.

Advanced

You might wonder about the significance of the :: in the first entry of the .jab context dictionary. It is there to prevent the value list of the context dictionary from collapsing to a simple list in case all the variables should have values of the same scalar type. This would then prevent subsequent definition of variables of other types in that context.

As they say in certain quarters, q eats its own dog food, meaning all ordinary operations can be performed on a context dictionary. For example, we can look up the values associated with `a in the root and with `wrong in the .jab context.

q)`.[`a]
42
q)`.jab[`wrong]
43

In fact you can use this to access an obscured global inside a function.

q)a
42
q){a:43; `.[`a]}[]
42

While this sort of thing makes for cute parlor tricks, we do not advise using it in real code.

Use get and set to retrieve and store global variables by name, especially inside functions.

 q){`a set 1+ get `a}[]
 `a
 q)43
 43

12.5 Expunging from a Context

We have seen that a context is a directory that maps a context’s entity names to their values. This means that in order to expunge an entity from a context, we could directly remove it from the dictionary. But this is bad practice since it uses the underlying implementation and the actual expression is prone to typing error.

Instead, q provides a special overload of the delete template explicitly for this purpose.

q)a:42
q).jab.wrong:43
q)delete a from `.
`.
q)a
'a
q)delete wrong from `.jab
`.jab
q)\v
`symbol$()
q)\v .jab
`symbol$()

Indeed, we issue the \v command to display the names of variables in a context (current context if none is specified) to verify that the variables are gone.

12.6 Saving and Loading Contexts

Since a context is a dictionary, you can persist it – and all its contents – as a serialized q object using set.

For example, to write out the root context, retrieve it by name and set it into a file.

q)`:/data/root set get `.
`:/data/root

Conversely, you can read the serialized object back into memory using get on the file and then overwrite the current root context using set

q)`. set get `:/data/root

If you organize your globals carefully, this makes check-pointing the state of your application very simple. Overlaying the root context replaces all its entries. As the saying goes, “Be careful what you ask for.”

12.7 Working in a Context

We return to the analogy of paths in the file system. The operating system keeps track of the current working directory for each program. This is merely a bit of state that points to a logical location in the file system. The main feature is it allows paths for items at or below the current working directory to be specified in relative form.

For example, if we start with the current working directory as the root and we wish to access a file /db/2015.03.01/trades.dat, then we must use the full path. However, if we issue the OS command,

cd /db

then /db becomes the current working directory and we can specify the path of the file as simply 2015.03.01/trades.dat. Note the absence of the leading /, which signifies a relative path as opposed to an absolute one. This provides significant simplification for deeply nested directory structures, which often arises in large development environments.

A q session has a similar concept. At any point in the session there is a current working context. At the beginning of a fresh q session, the current working context is the root context and you use absolute names for all global variables. We can change the working context with the \d command (for “directory” by analogy to the file system). After you change the current working context, you can use relative names for global variables. Here we create two globals in the root context and then switch the current working context to show relative names.

q).jab.util.counter:0
q).jab.util.incrctr:{[] `.jab.util.counter set 1+get `.jab.util.counter}
q).jab.util.incrctr[]
`.jab.util.counter
q)\d .jab
q.jab)util.incrctr[]
`.jab.util.counter
q.jab)util.counter
2

Notice that the q prompt changes to let you know the current working context.

There is a subtle point about how globals are bound during function definition that we must point out. To illustrate, start a fresh q session and do the following.

q)state:`NY
q).jab.f1:{[] state}
q)\d .jab
q.jab)state:0N
q.jab)f2:{[] state}
q.jab)\d .
q).jab.f1[]
`NY
q).jab.f2[]
0N
q).jab.f1
{[] state}
q).jab.f2
{[] state}

We began by defining a variable state in the root context to hold a two-character code for one of the fifty United States of America. Also from the root context, we define a function f1 in the .jab context that returns the value of state from the root context. Next we switch to the .jab context and define a variable state that holds the integer value of a state machine, along with a function f2 that returns the value of this global.

We return to the root context, apply each function using an absolute path name and observe the expected values. Namespacing has done its job by avoiding a clash between the disparate state variables, so what’s the issue? Look closely at the display of the two function bodies. They are identical! How does q know which state variable to access at runtime?

Namespacing Fact of Life #3

Any unqualified global reference in a function is bound to the current working context in effect at the point of the function definition. That context is not explicitly displayed in the function body.

This has the undesirable consequence of requiring a potential user of the function to know the circumstances of its definition.

But wait, there’s more. Let’s redo the initial example of this section in a fresh q session, but change the current working context two levels down instead of one.

q).jab.util.counter:0
q).jab.util.incrctr:{[] `.jab.util.counter set 1+get `.jab.util.counter}
q)\d .jab.util
'.jab.util

You can’t do it!

Namespacing Fact of Life #4

You can only set the current working context one level down from the root. This is truly unfortunate.

You might be tempted to abandon namespacing altogether, but don’t. If you adhere to the following recommendations, you will avoid the issues mentioned above and namespacing will work effectively.

Recommendations for Namespacing:

  • Use namespacing at arbitrary depth to create hierarchical organization of global variables, both data and functions.
  • Keep related functions and any globals they require together in an appropriate context in the hierarchy with a descriptive name.
  • By convention, q namespaces are all lower case.
  • Define all global entities from the root context using fully qualified names.
  • Always refer to global entities using fully qualified names.

Essentially the last two recommendations amount to avoiding all use of \d to switch the current working context. Here is one way to define the functions f1 and f2 above in a fresh q session so that everything is explicit.

q).jab.geog.state:`NY
q).jab.geog.f1:{[] .jab.geog.state}
q).jab.machine.state:0N
q).jab.machine.f2:{[] .jab.machine.state}
q).jab.geog.f1
{[] .jab.geog.state}
q).jab.machine.f2
{[] .jab.machine.state}
Back to top