How do I execute a q function call with parameters over IPC?

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}   // server
q)hypotenuse[3;4]
5f
q)
q)phandle (`hypotenuse; 5; 12)    // client
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}    // server
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     // server
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}    // client
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                 // server
'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)                // client
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"      // client
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.