Control Flow

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:

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:

Best practices

  1. Explicit argument declaration: only use x,y,z if the function is very simple.
  2. Don't do too much in one line: if you see a function has multiple layers of brackets, break it up into smaller functions.
  3. Indent if statements properly
  4. 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