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
Tags: Q
July 24, 2009 at 10:17 pm |
Hi there! Good to see the ranks of Q are getting more programmers oriented functionally.
I’m more of a Haskell enthusiast than serious practitioner, but I have been programming in Q for a while. Thought I’d comment a little on the translations you’ve provided.
There are built in functions for head and tail (first and last).
Intersperse can be more succinctly written as:
intersperse:{raze ((-1 _ y),\:x),-1#y}
This uses the each-left, which works like the apostrophe each adverb, but only across the left join element.
If you’d like to turn a function composition into a function itself, you need only surround it with parentheses. So, for example, zip becomes:
zip:(,’)
Also, there is a built in map function: each
q)enlist each 1 2 3
1
2
3
q)enlist each 1
,1
If you haven’t already, you should email Simon or Charlie about getting access to the K4 list box.
July 26, 2009 at 1:45 pm |
This works only of you intersperse an atom into a list. If you intersperse a list into a list of lists it doesn’t do the right thing:
Also
zip:(,’)zips correctly only lists of atoms.q)zip:(,') q)first first zip [((1;1);(2;2);(3;3)); ((4;4);(5;5);(6;6))] 1 q)zip: { x {(x;y)}' y } q)first first zip [((1;1);(2;2);(3;3)); ((4;4);(5;5);(6;6))] 1 1