Short answer:

q)phandle: hopen `:serverhost:5012
q)phandle (function; arg1; arg2 ..)
..
q)hclose phandle
q)

To invoke a function in another q process (i.e., a function that already exists in that q process), you need the following:

  1. a process handle to the other q process,
  2. the name of the pre-existing function in the other process, and
  3. the parameters you want to pass.

Then all you need to do is to apply the process handle to a list whose first element is the function name (as a symbol) and whose remaining elements are the arguments.

For example, let’s start one q process, which will be our server, listening on TCP port 5012. In our server, we’ll define a function square (we’ll make the background color different for the server to make it easier to distinguish from the client):

$ q -p 5012             // server
..
q)\p                    // display the listening port
5012
q)square: {x * x}
q)square 3
9
q)

Next, we’ll start up another q process (the client), create a process handle connected to the first q process (the server), and request the first process to compute square 5:

$ q                     // client
..
q)phandle: hopen `:localhost:5012
q)phandle (`square; 5)  // remote execution
25
q)

To call a function with more parameters, simply add them to the end of the list. We’ll demonstrate by defining a 2-argument function on the server that calculates the length of a right triangle’s hypotenuse:

q)hypotenuse: {sqrt sum square x,y}
q)hypotenuse[3;4]
5f
q)
q)phandle (`hypotenuse; 5; 12)
13f
q)

What if the function you’re calling doesn’t take any parameters? For example, we’ll define a function in the server called serverTime that returns the local time according to the server:

q)serverTime: {[] .z.T}
q)serverTime[]
11:51:34.762
q)

You can’t pass zero parameters over IPC:

q)phandle enlist `serverTime  // a list with just
'length                       // the function name
q)                            // is not allowed

However, you can pass whatever you like, and it won’t matter:

q)phandle `serverTime`ignored
11:52:28.537
q)phandle (`serverTime; ())    // () is often used
11:52:40.923
q)phandle (`serverTime; ::)    // so is ::
11:52:47.338
q)

See :: (generic null).

So far, all of our examples involved a client invoking a predefined function on the server. You can also pass a function defined on the client to be executed on the server. To see this, let’s define a global variable on the server:

q)SERVERGLOBAL: 47
q)

Now, on the client, we’ll define a function called getSERVERGLOBAL to retrieve the value of SERVERGLOBAL on the server. Instead of passing the name of the function (i.e., `getSERVERGLOBAL), we pass the function’s value:

q)getSERVERGLOBAL: {[] SERVERGLOBAL}
q)getSERVERGLOBAL[]
'SERVERGLOBAL                   // not defined here
q)phandle (getSERVERGLOBAL; ::) // note the missing `
47
q)

Notice that this operation does not cause getSERVERGLOBAL to become defined on the server:

q)getSERVERGLOBAL
'getSERVERGLOBAL
q)

This technique can be used to run built-in functions and anonymous functions (aka lambdas) on the server as well:

q)phandle (+; 2; 4)
6
q)phandle ({til SERVERGLOBAL - x}; 42)
0 1 2 3 4
q)

There is one more way to convey code to the server to run: you can pass the code in a string.

q)phandle "SERVERGLOBAL + 4"
51
q)

We prefer passing a function over a string, because – especially as the expression to be passed gets more complex – it’s easier to read.

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}

Consider the following function, testfunc:

testfunc: {[] x: `aaa;
y: `bbb;
z:: `ccc;
-1 “finished!”;
}

Let’s demonstrate the placement of a breakpoint prior to the assignment of global variable z. Although there is no explicit support for breakpoints in q, insertion of non-compliant code, such as breakhere; shown below, does the job (don’t forget the trailing semicolon):

testfunc: {[] x: `aaa;
y: `bbb;
breakhere;
z:: `ccc;
-1 “finished!”
}

We are tricking q into throwing a signal ‘breakhere.

q)testfunc[] {[] x: `aaa;
y: `bbb;
breakhere;
z:: `ccc;
-1 “finished!”
}
‘breakhere
q))

At this point, we can examine the local stack.

q))x
`aaa
q))y
`bbb
q))z
()
q))

kdb has suspended execution, leaving the remaining two lines of function testfunc unexecuted. : (colon) resumes execution.

q)):
finished!
-1
q)x
‘x
q)y
‘y
q)z
`ccc
q)

See: global amend (::)