It has been six months since I started to earn my living by writing code in Q. I am still not a guru (a Q god as they call it in Q circles), but not a complete newbie either.
Things I like about Q
Q is terse. I will not write much about that. Other people argued better than I could that succinctness is power.
Q is readable. This may be a surprise for anybody who looks at Q code for the first time. Q syntax uses a lot of single letter characters or short combinations like /: as keywords. Its terseness is typically considered as decreasing readability.
I remember when I was a TA I taught a math class called “Personal business decisions” where one of the things we did was translating pieces of tax code into math formulae. The tax code was written in typical lawyerese that needed to be analyzed, broken into pieces, understood and then expressions like “amount in excess of the aforementioned value if any” converted to math expressions like . It was amazing to see how a one third of a page of text was turning into a line or two of math. The resulting formulae were much more readable for me as I could see in a glance what was being expressed. However for most students they were equally misterious as the original in lawyerese.
My point in telling the story is that terseness may increase readability but only after an effort is made to train your eye to automatically parse the syntax. If you don’t know the syntax then longer, more descriptive expressions seem more readable. As one learns the syntax of a terse language like Q the optimum for readability quickly shifts towards the less verbose style.
Q supports well functional style of programming. In particular it supports currying. For example suppose we define two functions: one that doubles its argument and a second, three parameter one that represents a linear function
Then we can do regular Haskell style currying like
q)lin[1;2] double 3 q)13
Here lin[1;2] fixes first two variables (x,y) in lin and makes it a unary (monadic as they say in Q) function of the third variable.
Q, however, can do better than that: it can do sections. You can fix any variables in a function to get a function of the remaining variables. So lin[;1;2] represents and lin[1;;3] represents while lin[;;3] represents .
The Q keyword “each” is roughly equivalent to Haskell’s “map”. Binary (dyadic) functions can be written infix, so we can write like this:
q)double each 1 2 3 4 2 4 6 8
Who can say this is not readable?
Writing an apostrophe right after a binary (dyadic) function makes it accept lists (of the same length) on both sides and compute the function component-wise. So we can do something like
q)(1 2 3) lin[;4;]' 5 6 7 q)21 26 31
This calculates the list lin[1;4;5],lin[2;4;6],lin[3;4;7]. Maybe it takes a little bit to get used to such modifiers (adverbs as they are called) but they are very handy.
Q is easy to learn. It is not as easy as Python, but definitely easier than Haskell. Of course, as with every language, true mastery requires years of experience, but if you are in environment where people are willing to share knowledge (as I’m lucky to be) you can get productive in two-three weeks.
Finally, working with Q is fun. There is an element of boredom in every job. The main source of boredom are repetitive tasks, things that you have to do over and over again. There is very little boilerplate in Q code.
What I don’t like in Q
Q is not free software. I wouldn’t use it for any at-home for-fun project, even if I could afford the license which I heard is very expensive.
Q is dynamically typed. It’s a matter of taste I guess but I like it when the type system protects me from my mistakes, like Haskell does. Run time errors are embarrassing.
Q is quirky. By quirky I mean that from behavior in one area one can not reliably extrapolate to similar area. One of the source of quirkiness is heavy overloading of keywords and built in functions that may do quite different things depending on what are types of their parameters. Take function sv for example. It can concatenate strings interspersing them with a separator. Very useful. Applied to symbol lists it may concatenate them with dots or sometimes with slashes, if the first symbol is a file handle. Applied to integers it will calculate sv . I heard rumours (and I am not making this up) that if you set the parameters right, sv will solve sudoku for you.
Another example of quirkyness is exceptional treatment of lists of dictionaries keyed by symbols.
Q allows nonhomogeneous lists. Usually you can concatenate two homogeneous lists to form nonhomogeneous ones. For example you can concatenate a singleton float list and singleton integer list (comma roughly corresponds to Haskell’s “++”).
q)a:enlist 1.0; q)b:enlist 1; q)a,b (1f;1)
You can do a similar thing with lists of tables:
q)a: enlist ( col1: 1 2 3; col2: 3 4 5) q)b: enlist ( col3: 6 7 8 ) q)a,b
Or lists of dictionaries (“!” with lists on both sides builds a dictionary with key on the left side and values on the right):
q)a:enlist (1 2 3)!3 4 5 q)b:enlist (2 3 4 5)!1 2 3 4 q)a,b (1 2 3!3 4 5;2 3 4 5!1 2 3 4)
But here is the quirk: in general you can’t do the same with dictionaries keyed by symbols:
q)a:enlist (`a`b`c)!1 2 3 q)b:enlist (`d`e)!4 5 q)a,b 'mismatch
Although there is no problem if the dictionaries have the same key:
q)a,a a b c ----- 1 2 3 1 2 3
This result also explains what happens. Singleton lists of dictionaries keyed by symbols are silently converted by Q to tables with one row. And tables can be concatenated (with comma) only if they have the same columns…