My new job will involve coding in Q. Q is a scripting language derived from APL. It is mostly used for stuff related to trading and finance. I don’t know it yet, I’m supposed to learn it on the job, so I decided to have a look at it.

As an exercise, I took a couple of Haskell functions in the standard Data.List module and tried to see how one can express them in Q. Experienced Q developers (called “Q gods” in the Q terminology) will probably laugh at my code, but well, I have to start from something. Any corrections and idioms from Q gods are welcome.

Here are a couple of trivial functions that are special cases of builtin Q functions

init: {-1_x} tail: {1_x} head: {x[0]}

Q is a functional language. It allows to take functions as parameters in functions. Here the takeWhile function takes a predicate as its first parameter. The code is probably too imperative for Q, I am sure there is a better way.

takeWhile:{[p;xs] i:0; while[(i < count xs) & p[xs[i]]; i+:1]; xs[til i] / quite readable, isn't it }

Q supports anonymous functions so one can call takeWhile as follows:

q) takeWhile [{x>0}; (1;2;4;0;3;4;5)] 1 2 4

Here {x>0} is an anonymous function that returns true (1b) when the argument is positive and 0b when it is less or equal 0.

My first shot at Haskell’s intersperse looked like this:

intersperse: { -1 _ raze y ,' x }

Here y ,' x creates pairs with first element taken from y and second equal to x:

(1;2;3) ,' 0 1 0 2 0 3 0

raze removes one level of nesting so we get a list instead of a list of pairs:

raze (1;2;3) ,' 0 1 0 2 0 3 0

-1 _ then drops the last element

This works well, but only when the first argument is an atom (not list).

intersperse[0;(1;2;3)] 1 0 2 0 3

But when we want to intersperse a list we get an error:

q) intersperse [(0;0);((1;1);(2;2);(3;3))] {-1 _ raze y ,' x } 'length

To get a more general version we first define Haskell’s replicate:

replicate: {[n;e] a: {x, (enlist z)}; () a[ ; ;e]/ (til n) / folds the dyadic (binary) function a[ ; ;e] over a list of length n }

Here a: {x, (enlist z)} creates a triadic (ternary) function that appends the third argument to the first and ignores the second.

This replicate implementation seems too complicated for such simple task, but I was unable to figure out anything simpler.

We also need Haskell’s zip :

zip: { x {(x;y)}' y }

And now we can define intersperse:

intersperse: { -1 _ raze zip [y ; replicate[count y;x]] }

This works better than the first attempt:

q) intersperse [(0;0);((1;1);(2;2);(3;3))] 1 1 0 0 2 2 0 0 3 3

Haskell’s map is kind of redundant in Q as unary functions (called “monadic” in Q, isn’t that funny?) get mapped when called with a list as an argument.

q)(3+)4 7 q) (3+)(1;2;3) 4 5 6 q)

This approach causes problems though when we want to map a function that works both for atoms and lists. One such function is enlist , which creates a singleton list in Q, kind of like Haskell’s return in the list monad.

(enlist 3)[0] 3

So how can we map enlist over a list? Just calling it on a list gives a singleton list with the argument as the only element of the result:

q) count (enlist (1;2;3)) 1

The only solution I can see is to use a trick with folding similar to the one in the definition of replicate above.

map: { [f;xs] a: {x, (enlist z[y])}; () a[ ; ;f]/ xs }

This works:

q)map[enlist; (1;2;3)] 1 2 3

Share this: Twitter

Facebook

Like this: Like Loading... Related

Tags: Q