Functions
The minimum and maximum number of arguments a function can take in q is zero and eight.
Zero parameter: niladic
One parameter: monadic
Two parameters: dyadic
Function syntax
Defining a function:
func:{[param1; param2]
// do something
};
Calling a function:
func[param1; param2]
Special cases:
- To call on a list of values:
func[var1 var2; param2]
- To call within a query:
select newCol:func[col1; col2] from tbl
Function forms
Operators can be used as if they were functions. Just pass the operands as arguments.
q) 1 in 2 3
q) in[1; 2 3] // returns 0b
Projections
Projections are a way to create new functions by fixing some arguments of an existing function.
For example, to create a function that adds 5 to its argument:
q) add5: 5+
q) add5: +[5;] // equivalent but in functional form
q) add5: +[5] // also works; another way to write in functional form
q) add5 10 // returns 15
Note that it has to be defined as
5+ instead of
+5.
i.e., the number has to be the first operand/argument.
But if you're using functional form, order doesn't really matter.
Variable scope
Same as other languages, variables defined within a function are local to that function.
To define a global variable within a function, use the
:: operator, which can also be writen as
set.
q) myFunc: { b::10; `c set 20 } // b and c are global variables
q) delete b c from `. // delete global variables
Note that there are minor differences between using
:: and
set:
- If using
::, if there is a local variable with the same name, it does not overwrite that
local variable b
- If using
set, if there is a local variable with the same name, it overwrites that local
variable c as well.
Best practices
- Explicit argument declaration: only use x,y,z if the function is very simple.
- Don't do too much in one line: if you see a function has multiple layers of brackets, break it up into
smaller functions.
- Indent if statements properly
- Don't use
: to return at the end because by default q returns the last expression
evaluated. Only use it if you want to return early, or if it's in an if statement.
If statements
Two ways to do if statements.
The first way only contains the if part, no else part.
if[condition;
do this if true;
then do this;
]
By default, this type of if does not return anything. If you want to return, you have to use
: to
exit early. And you have to wrap this if statment in a function, like this:
myFunc: {[]
if[condition;
do this if true;
then do this;
: return some value;
];
}
The second way contains both if and else parts. It needs to be done with
$.
$[condition;
[
do this if true;
then do this;
];
do this if false;
]
By default, this structure returns either the last statment in the true block or the false block, so you don't
need to wrap it up. But if you put a semicolon at the end of the last statement (like above), it won't return
anything.
With this structure, you can have nested if-else.
fizzbuzz: {[]
$[0=x mod 3;
$[0=x mod 5;
`fizzbuzz; // divided by both 3 and 5
`fizz // divided by 3 only
];
0=x mod 5;
`buzz; // divided by 5 only
x // not divisible by either
]
}
You can also do something like a switch in other languages:
grade: {[] $[x>=90; `A; x>=80; `B; x>=70; `C; x>=60; `D; `F]}
Addtionally, you can pass multiple conditions as a boolean list:
?[10001b;
1 2 3 4 5; // true block
10 20 30 40 50 // false block
] // returns 1 20 30 40 5
This can be very handy when the input is a list. For example, to replace all negative values in a list to its
absolute value, you can just do
replaceNegative: {[] ?[x<0; -1*x; x]}
Try-catch block
Try-catch block is called trap
@ in q. The syntax is:
@[function; argument; errorHandler]
This basically says, pass the arguments to the function. If it runs successfully, nothing happens. But if it
fails, the errorHandler will be run.
The errorHandler is a function that takes one argument, which is the error message.
For example:
protectedSin: {[x] @[sin; x; {`errorOccurred}]}
Extend trap
This is try-catch block with backtrace.
For try-catch with
@, the error function only takes one argument, which is the error message.
But with extend trap
.Q.trp, you can pass two arguments, the error message and the backtrace.
To make the backtrace more human-readable,
.Q.trp is usually used together with
.Q.sbt.
.Q.trp[f; `three; {2@"Error is :",x,"\nBacktrace:\n",.Q.sbt y; -1}
Iterators
Most iteration is handled by q operators implicitly. e.g.,
1 2 3 + 4 5 6
But implicit iteartors require that two operands are equal in length.
When you need to iterate over two lists of different lengths, you need to use an explicit iterator.
Each and peach
each only works on functions with one parameter, such as
f[;y] in the example above
and
count.
If you have a function with more than one parameter, such as
in, you need to fix some of the
parameters using projections. For example you can do
5 in to create a function that checks if 5 is
in a list.
peach, aka, parallel each is jsut a multi-threaded version, same stuff.
q) L:("the";"quick";"brown";"fox")
q) count each L // only one parameter L, used on the function count
3 5 5 3
q) each[count; L] // same as above; functional form
Each-both '
Each-both works on two lists in a pairwise fashion.
each-both ' works on functions with two parameters, for example
take #
q) L:("the";"quick";"brown";"fox")
q) 3#'L
"the" // take 3 from "the"
"qui" // take 3 from "quick"
"bro" // take 3 from "brown"
"fox" // take 3 from "fox"
Pay attention to the syntax here, don't think of
' as a function that takes three parameters.
Instead, think of it as something that can be attached to a function that takes two parameters, and creates a
"new" function that takes two arguments.
q) #'[x; y] // this works too; functional form
q) '[#; x; y] // this is NOT going to work
Each-both is actually what's under the hood when you do
+ and other operators on two lists.
Each-left \: and each-right /:
Again, think of each-left and each-right as something that can be attached to a function that takes two
parameters, and creates a "new" function that takes two arguments.
Each-left: applies
each element on the
left to the whole list on the right.
Each-right: applies
each element on the
right to the whole list on the left.
q) 1 2 +\: 3 4 5 // each-left
4 5 6
5 6 7
q) 1 2 +/: 3 4 5 // each-right
4 5
5 6
6 7
Each-prior ':
each-prior ': is also known as
prior.
And again, think of it as something attached to a function to modify its behavior.
When you attach this to a function that takes two parameters, it will apply the function to each element in the
list and the
prior element in the list.
q) 0 +': 10 20 30 40 50
q) +'[10 20 30 40 50] // alternatively, use functional form, the first parameter is by default 0 or null
10 30 50 70 90
How do we get the list? 0+10, 10+20, 20+30, 30+40, 40+50.
Scan and over
Scan and over are accumulators, meaning they carry over the result of the previous computation to the next.
Because of this, they only work with functions with two parameters.
q) +/ 1 2 3 4 5 // over: only shows the final result
15
q) +\ 1 2 3 4 5 // scan: shows the intermediate results
1 3 6 10 15
q) *\ [11; 1 2 3 4 5] // the argument can be a list
11 22 66 264 1320
If you want to mimic the bahavior of a for/while loop on a function
f:2*, you can do something like
this with scan (same thing works for over):
q) 3 f\2 7 // this is similar to: for i in range(3), apply f to the list [2;7]
4 14
8 28
16 56
q) {last[x]<20} f\2 7 // this is similar to while last element in the list is less than 20, apply f to the list [2;7]
4 14
8 28 // 28 breaks the while loop
Do and while
DO NOT USE THEM!!
They are very slow. But the syntax is like this:
q) do[noOfTimes; expression1; expression2; ...]
q) while[condition; expression1; expression2; ...]
There is almost always a way to use the iterators above to replace them.
q) do[count prices; result,; prices[i]*quantities[i]] // BAD, BAD, BAD
q) result: prices * quantities // GOOD
q) customizedFunc'[prices; quantities] // if there's no built-in function, use each-both