Entries in amend (5)

How does fby work?

According to code.kx.com, fby is short for filter-by, and it is commonly used as the q equivalent to SQL's HAVING clause (though, like where, fby is a q function, and its use is not limited to where clauses).

What fby does is aggregate values from one list based on groups defined in another, parallel, list. For example, suppose we have one list of cities and another list with a few temperature samples for each. We can use fby to calculate the minimum temperature sample for each city, and then replicate those values at each position for each corresponding city:

q)city:`NY`NY`LA`SF`LA`SF`NY
q)temp:32 31 75 69 70 68 12
q)(min;temp) fby city
12 12 70 68 70 68 12
q)

Thus, NY's minimum temperature, 12, appears at every index in fby's output where `NY appears in city.

At the core of fby (like by) is group, which organizes the distinct values in a list into a dictionary mapping those values to their indices:

q)city:`NY`NY`LA`SF`LA`SF`NY
q)group city
NY| 0 1 6
LA| 2 4
SF| 3 5
q)

We can group the temperatures for each city together by indexing temp with the value of the grouped city dictionary:

q)grouped: value group city
q)temp[grouped]
32 31 12
75 70
69 68
q)

Note that the result of indexing temp with grouped is a nested list with the same shape as grouped. This is a general principle: the result of an indexing operation has the shape of the index.

Now we can apply an aggregation function to each of the temperature groups:

q)min each temp[grouped]
12 70 68
q)

We're almost there. The real trick of fby is placing each aggregation result into a new list so that each element has the correct value per the grouping list. We can use @ (functional amend) to get the job done (see also the functional apply/amend faq):

q)@[temp; grouped; :; min each temp[grouped] ]
12 12 70 68 70 68 12
q)

The real fby is just slightly more complicated to ensure that the first argument to @ has the correct type.

How do I use the functional forms of apply and amend?

Typical q code operates on all of the elements of a container at once:

q)container: 1 2 3
q)100 * container
100 200 300
q)

Sometimes we are interested in only a subset of elements from a container:

q)container: til 10
q)container
0 1 2 3 4 5 6 7 8 9
q)container[where 0 = container mod 2]
0 2 4 6 8
q)                                                                                                         

Sometimes, however, you need to update particular elements of a structure while leaving the remaining elements unchanged. That's what functional apply and amend are for; they transform specific elements of a container without touching the others. The variations are distinguished by 3 choices:

1. @ or .

Which operator is used, @ or ., determines the interpretation of the indices used to select the elements to transform.

2. container or name

The first argument is either the value of a container or the name of a global variable referring to a container. In the former case, a new object is returned; in the latter, the global variable is modified and its name is returned.

3. monadic or dyadic function

If the transformation requires additional information beyond that contained in each element itself, that is accomplished by using a dyadic function and supplying the additional information in a fourth argument to the operator.

For a detailed discussion, please see the following faqs:

Functional @ (apply)
Functional . (apply)

Lastly, note that there is another pair of overloads for @ and . - each with three arguments - called protected execution, which are invoked when the first argument to @ or . is a function or projection; protected execution is discussed in another faq.

How do I use the functional form of @ (at) apply?

Short answer:

1. @[container; indices; function]
2. @[container; indices; function; second_args]

(Note that there is another overload for @ with three arguments, called protected execution, which is invoked when the first argument to @ is a function or projection; protected execution is discussed in another faq.)


In the 3-argument case, q applies function to those elements of container specified by indices, leaving the rest of container alone. In other words, the behavior of 3-argument @ (apply) resembles that of the following function:

Although this model breaks down when the first argument is a global variable name, it is very helpful in understanding what is going on even in that case. Let's look at a couple of simple examples to clarify this:

q)list: 1 + til 8
q)list
1 2 3 4 5 6 7 8
q)@[list; 0; neg]         / negate element 0
-1 2 3 4 5 6 7 8
q)list                    / list remains unmodified
1 2 3 4 5 6 7 8
q)@[list; 1 3 4; {x * x}] / square elements 1, 3, and 4
1 4 3 16 25 6 7 8
q)

The diagram below illustrates the last example above:


Because the new container has the same type and shape as the original, function must return the same type as its argument, unless the container is a mixed list (i.e., type 0):

q)@[list; 4 5 6 7; {x div 2}] / list is an int list
1 2 3 4 2 3 3 4
q)@[list; 4 5 6 7; {x % 2}]   / but % returns float
'type
q)mixed_list: 1 2, `3`4
q)type mixed_list
0h
q)@[mixed_list; 0 3; string] / anything goes with type 0
,"1"
2
`3
,"4"
q)

Next, we'll consider the second case listed at the top of this faq. When function is dyadic and a fourth argument is supplied, @ (apply) behaves like the following function:

The basic idea is still the same, i.e., to transform selected elements while leaving the rest of the container intact. The difference is that, instead of function being modified by each, it is modified by '(each-both) (also see this faq on each-both and multivalent each), so that the selected elements of container are paired up with the corresponding elements from second_args:

q)@[list; 1 3 5 7; +; 10 20 30 40]
1 12 3 24 5 36 7 48
q)@[list; (1 3 5 7; 0 2 4 6); *; 2 -2]
-2 4 -6 8 -10 12 -14 16
q)

One function often used with 4-argument @ (apply), is : (amend). We can replace selected elements with a certain value (or values) as follows:

q)list: 1 + til 8
q)@[list; 1 3 5 7; :; 47]
1 47 3 47 5 47 7 47
q)@[list; 1 3 5 7; :; 10 20 30 40]
1 10 3 20 5 30 7 40
q)

It might appear that the above example exposes a flaw in our model, apply_dyadic, of @ (apply). However, recall that, when modified by an adverb, : (amend) does not modify its first argument:

q):'[list 1 2 3; 20 30 40]
20 30 40                     / :' returns its 2nd argument
q)list                       / without modifying its 1st
1 2 3 4 5 6 7 8
q)

Where our models do break down is when, as alluded to earlier, the first argument is the name of global variable referring to a container, rather than the value of a container. In that case, the mechanics of the operation are the same, but original container is modified, and the return value is the name:

q)list: 1 + til 8
q)@[`list; 0; neg]
`list
q)list
-1 2 3 4 5 6 7 8
q)

This behavior is handy for writing functions of your own that work in both scenarios - either creating a new value or modifying one in place - like the following:


We can also use @ to apply a function to selected entries in a dictionary:

q)dictionary: `a`b`c`d ! `1`2, 3 4
q)dictionary
a| `1
b| `2
c| 3
d| 4
q)@[dictionary; `a`b; {"I"$ string x}]
a| 1
b| 2
c| 3
d| 4
q)

Tables can be transformed as well. Consider the following table:

q)t: ([] col1: `foo`bar`baz; col2: 5 10 15f)
q)t
col1 col2
---------
foo  5   
bar  10  
baz  15  
q)

Recall that each row of a table is a dictionary:

q)t 0
col1| `foo
col2| 5f
q)

Thus, we can use @ (apply) to accomplish an update in the following manner:

q)update col2 - 4 from t where i > 0
col1 col2
---------
foo  5   
bar  6  
baz  11  
q)@[t; 1 2; {[row] row[`col2] -: 4; row}]
col1 col2
---------
foo  5   
bar  6  
baz  11  
q)

We could also obtain the previous result using nested calls to @ (apply):

q)@[t; 1 2; @[; `col2; -[; 4]]]
col1 col2 
---------
foo  5 
bar  6
baz  11
q)

Since we can index tables using column names, we can double all of the entries in col2 as follows:

q)@[t; `col2; 2*]
col1 col2
---------
foo  10  
bar  20  
baz  30  
q)

How can I capture STDOUT, STDERR and the exit status of a system command invoked from q?

Short answer: 2>&1 (i.e., redirect STDERR to STDOUT), echo $?, and add parentheses.

Note: This applies to bash, and has not been tested in csh, ksh, etc.


We can invoke arbitrary external programs from q using the system command. For example:

q)system "echo Hello, world!"
"Hello, world!"
q)

External programs often signal errors by returning a non-zero exit code, which is stored in the shell variable ?:

$ ls nonexistent_file
ls: nonexistent_file: No such file or directory
$ echo $?
1
$ foo
-bash: foo: command not found
$ echo $?
127

When an external program called from system in q runs into a situation like this, q detects the non-zero exit code from the child process and raises a signal, 'os:

q)system "ls nonexistent_file"
ls: nonexistent_file: No such file or directory
'os
q)

When trying out ideas at the console as in the above example, the full details of the error are available on the screen. Getting those error details programmatically is a bit trickier, however. Consider the following function, in which we use protected execution to invoke an error handler when the 'os signal is raised:

use_protected_execution: {[]
    : @[system;
        "ls nonexistent_file";
        {[error_info] -1 "error is ", error_info;}];
    }

The error handler in use_protected_execution knows only that an operating system error occurred. None of the error details are available within the code:

q)use_protected_execution[]
ls: nonexistent_file: No such file or directory  // invisible
error is os
q)

We can improve the situation somewhat by appending "; echo $?" to the command:

q)result: system "ls nonexistent_file; echo $?"
q)result
,"1"    // actually a list of strings
q)

This enables us to distinguish error codes as follows:

distinguish_error_codes: {[]
    result: system "ls nonexistent_file; echo $?";
    exit_code: "I" $ last result;
    $[0   = exit_code;
        ; // everything's alright
      1   = exit_code;
        ; // minor problem
      2   = exit_code;
        ; // major problem
      127 = exit_code;
        ; // command not found
    / else
        // unhandled exit code
      ]}

What we really want, though, is to capture the output sent by the failed command to STDERR. You might expect the usual shell redirection syntax to do the job:

q)result: system "ls nonexistent_file 2>&1; echo $?"
ls: nonexistent_file: No such file or directory
q)result
,"1"
q)

No such luck. The trick is to put parentheses around the whole thing:

q)result: system "(ls nonexistent_file 2>&1; echo $?)"
q)result
"ls: nonexistent_file: No such file or directory"
,"1"
q)

We can wrap all this up in a handy function that, instead of raising os, raises the descriptive message output to STDERR by the child process:

call_external: {[command]
    result: system "(", command, " 2>&1; echo $?)";
    exit_code: "I" $ last result;
    if [0 < exit_code;
        ' raze -1 _ result];
    -1 _ result}

When does : (amend) not modify its first argument?

Short answer: When 1) projected or 2) modified by an adverb, e.g., :/: :\: or :'


Normally, the : (amend) function is used to assign a value to a name:

q)foo: 47
q)foo
47
q)

Like other built-in functions that take two arguments, : (amend) can be called using either infix notation (as above) or function call notation:

q):[foo; 747]
q)foo
747
q)

Note that : (amend) displays its special semantics (which it shares with assignments in all strict languages) of not evaluating its first argument when that argument is simply a name, regardless of whether : (amend) is invoked infix or functionally.

q)delete bar from `.    / make sure bar is not defined
q):[bar; 42]
q)bar
42
q)

On the other hand, : (amend) does evaluate its first argument when that argument is an expression, but : (amend) still produces l-values when it does so:

q)list: 0 1 2 3
q)list[0]: 47
q)list
47 1 2 3
q):[list 1 2 3; 10 20 30]
q)list
47 10 20 30
q)

Based on the previous examples, you might be tempted create a projection from : (amend) and a left-hand side, but it turns out you can't:

q):[foo]
'foo
q)

It is possible to close the first argument to : (amend), but the resulting projection is no longer an assignment; it is an identity function:

q)foo: 42
q)(:[foo])[47]
47
q)foo
42
q)

In fact, the value attached to the first argument of : (amend) is irrelevant:

q)(:[`xyzzy])[47]
47
q)

Closing the second argument will cause : (amend) to return the same value always:

q)f: :[; 3]     / bind the 2nd argument
q)f 18          / no matter what we pass
3
q)f "hello"     / f will always return 3
3
q)

The other case when : (amend) does not perform assignment is when it is modified by an adverb:

q)list1: 0 1 2 3
q)list2: 4 5 6 7
q)(list1; list2) :\: 10 20 30 40   / we get the rhs
10 20 30 40                        / once for each list
10 20 30 40
q)list1                            / neither list
0 1 2 3
q)list2                            / has been modified
4 5 6 7
q)list2 :/: 0 1 2 3
0 1 2 3
q)list1[0 1 2 3] :' 10 20 30 40
10 20 30 40
q)