Ox Language Tutorial

Chapter contents:

Introduction
A first Ox program
Running the first Ox program
Variables, types and scope
Indexing matrices
Functions and function arguments
The for and while loops
The if statement
Operations and matrix programming
Arrays
Multiple files: using #include and #import
Object-oriented programming
Style and Hungarian notation
Optimizing for speed
OxGauss

Introduction

This chapter will give a brief overview of the important elements of the Ox language. A more formal description of the Ox syntax is in the syntax chapter.

A more comprehensive tutorial introduction to the Ox language is available as a separate book in PDF format. The accompanying tutorial programs are in the ox\tutorial folder. The complete reference is:

This is the recommended starting-point for learning the Ox language.

The next section will introduce the first Ox program, showing how matrix are used. We shall see that a program always includes header files to define the standard library functions, and that it must have a main function, which is where program control starts. We shall also see that the body of the function is enclosed in curly braces.

A first Ox program

Ox is an object-oriented matrix language with a comprehensive mathematical and statistical function library. Matrices can be used directly in expressions, for example to multiply two matrices, or to invert a matrix. The basic syntax elements of Ox are similar to the C, C++ and Java languages (however, knowledge if these languages is not a prerequisite for using Ox). This similarity is most clear in syntax items such as loops, functions, arrays and classes. A major difference is that Ox variables have no explicit type, and that special support for matrices is available.

The advantages of object-oriented programming are that it potentially improves the clarity and maintainability of the code, as well as reducing coding effort through inheritance. Several useful classes are provided with Ox.

As a first example of an Ox program consider the following Ox code:

myfirst.ox
#include <oxstd.oxh> // include Ox standard library header main() // function main is the starting point { decl m1, m2; // declare two variables, m1 and m2 m1 = unit(3); // assign to m1 a 3 x 3 identity matrix m1[0][0] = 2; // set top-left element to 2 m2 = <0,0,0;1,1,1>; // m2 is a 2 x 3 matrix, the first // row consists of zeros, the second of ones println("two matrices", m1, m2); // print the matrices }

Note the oxrun.dev button in the top-right of the example code: click this to open the code in a new oxrun.dev tab.

The program is in ox/samples/myfirst.ox; running this program produces the following result:

two matrices 2.0000 0.00000 0.00000 0.00000 1.0000 0.00000 0.00000 0.00000 1.0000 0.00000 0.00000 0.00000 1.0000 1.0000 1.0000

An Ox program consists of one or more source code files. All source files have the .ox extension. Header files are used to communicate declarations from one source file to another. Header files have the .oxh extension. The next section explains how to run the Ox program on your system. First we consider the myfirst.ox program in more detail:

An important advantage of Ox is that we can directly work with matrices, and do not have to worry about memory allocation and deallocation. Low-level languages may be faster, although we have encountered several cases in which Ox performed better than a comparable C program. Ox code has a much closer correspondence to mathematical expressions used on paper.

Running the first Ox program

Ox programs can be run in several ways, each differing in where the text and graphical output appears (or can be seen at all). We shall consider several in turn. The each of these are available under macOS, linux, and Windows, introducing minor variations. First, we show that Ox can be used directly in the browser as well.

oxrun.dev

To run some Ox code without installing OxMetrics or Ox console, go to oxrun.dev. On first use, this may take some time to load on a slow connection.

This is convenient for learning Ox: you can click on the oxrun.dev button for each example to open it in the browser. Furthermore, all the sample code for the Introduction to Ox can be loaded from the New button.

The browser version has some limitations compared to the installed version: it is more difficult to built on top of existing code or packages. It is also a bit slower because it is single-threaded and running in the browser. The advantage is that you can also try it on a phone or ipad: modern phones are fast enough to run Monte Carlo simulations.

OxEdit

When Ox Console is installed, the OxEdit front-end is part of it.

  1. Start OxEdit.
  2. Open the OxMetrics10 folder that was created in your Documents folder.
  3. Then from there, find myfirst.ox, and open it
  4. Run the code by clicking on the Run button:

There are several features to make programming easier. Syntax highlighting makes code easier to read, and helps spot some mistakes. You can select of block of lines, and then use Toggle Comment to temporarily comment code out.

And very importantly, there is context sensitive help with the F1 key.

The output will appear in a new window, entitled Ox Output.

Finally, a mistake results in an error message. Just Ctrl-click (Cmd-click on macOS) on the line with the error, and OxMetrics will jump straight to the location in the source code.

It is important to note that Ox programs can only be run from an open folder! Trying to run file that is opened in another way (e.g. through Save As to an unopened folder), results in an error message.

OxMetrics

OxMetrics is an extended version of OxEdit which includes interactive econometric modelling apps such as PcGive, STAMP and G@RCH. By the way: these have all themselves been written in Ox!

Using OxMetrics to run an Ox program is the same as OxEdit, except for the name of the output window.

Command line: oxl

Finally, we consider the command line, either from a terminal window under macOS/linux, or a command prompt under Windows.

Variables, types and scope

Variables are declared using the decl keyword. Unlike most low-level languages, variables are implicitly typed. This means that variables do not have a type when they are declared, but get a type when values are assigned to them. So a variable can change type during its lifetime. The most important implicit types are int for an integer value, double for a real number, string for a text string and matrix for a matrix (two-dimensional array) of real numbers. The next Ox program illustrates implicit declaration and scope:

decl1.ox
#include <oxstd.oxh>
main()
{
    decl i, d, m, s;
    i = 1;   // assign integer to i --> i is of type int
    d = 1.0;  // assign real number to d --> d is double
    s = "some text";//assign string to s --> s is string
    m = zeros(3,3);// assign to m a 3 x 3 matrix of zeros
                              // --> m is of type matrix
    println("i=", i, " d=", d, " s=", s, "\nm=", m);
}

This prints (\n is the newline character):

    i=1 d=1 s=some text
    m=
          0.00000      0.00000      0.00000
          0.00000      0.00000      0.00000
          0.00000      0.00000      0.00000

The scope of a variable refers to the parts of the program which can see the variable. This could be different from its lifetime: a variable can be `alive' but not `seen'. If a variable is declared outside any function, its scope is the remainder of the source file. It is possible to export such variables to other source files, as we shall see shortly.

Variables declared inside a function have scope until the closing brace of the level at which it is declared. The following example illustrates:

decl2.ox
#include <oxstd.oxh>
decl g_mX;                          // external variable
main()
{
    decl i = 0;                        // local variable
    {
       decl i = 1, j = 0;                       // new i
       g_mX = ones(3,3);
       print("i=", i, " j=", j);      // prints: i=1 j=0
    }         // brace end: local i and j cease to exist
    println("\ni=", i); // revert to old i, prints: i=0
}

The variable g_mX (here we use Hungarian notation), can be seen everywhere in the main function. To make sure that it can never be seen in other source files, prefix it with the word static (and rename it to s_mX). It is good programming practice to use static in such cases, because it is very useful to know that it is not used in any other files (we may than rename it, e.g., without any unexpected side effects). An example will be given in myfunc.ox below.

It is good practice to avoid global variables as much as possible: this makes code more self-contained, avoiding unwanted side effects. Object-oriented programming provides a way to do that, as do lambda functions with e.g. the maximization functions.

Indexing matrices

Indexing starts at zero, so m[0][0] is the first element of the matrix m. It is easy to select individual elements or a subset of the matrix. Here are some examples:

index.ox
#include <oxstd.oxh>
main()
{
    decl m = <0, 1, 2; 3, 4, 5; 6, 7, 8>;
    println("m = ", m);
    println("element 1,0: ", m[1][0]);
    println("second row: ",  m[1][]);
    println("second column: ", m[][1]);
    println("without 1st row/3rd col: ", m[1:][:1]);
    println("indexed as a vector ", m[2:3]);
    println("bottom right element: ", m[.last][.last]);
}

Which prints as output:

m = 
      0.00000       1.0000       2.0000
       3.0000       4.0000       5.0000
       6.0000       7.0000       8.0000
element 1,0: 3
second row: 
       3.0000       4.0000       5.0000
second column: 
       1.0000
       4.0000
       7.0000
without 1st row/3rd col: 
       3.0000       4.0000
       6.0000       7.0000
indexed as a vector 
       2.0000
       3.0000
bottom right element: 8

These expressions may also be used in assignments, for example:

    m[1:][:1] = 10;
    m[2:3] = m[6:7];

Functions and function arguments

We have already used various functions from the standard library (such as println, ones and zeros), and written various main functions). Indeed, an Ox program is primarily a collection of functions. It is important to know that all function arguments are passed by value. This means that the function effectively gets a copy which it can change without affecting the original. For example:

func1.ox
#include <oxstd.oxh>
func(mA)
{
    mA = zeros(1,2);
    print("ma in func()", mA);
}
main()
{
    decl ma;
    ma = ones(1,2);
    print("ma before func()", ma);
    func(ma);
    print("ma after func()", ma);
}

which prints:

    ma before func()
           1.0000       1.0000
    ma in func()
          0.00000      0.00000
    ma after func()
           1.0000       1.0000

If the function argument is not changed by the function, it could be good programming style to prefix it with the const keyword, as in:

    func(const mA)
    {
        print("ma in func()", mA);
    }

Of course it is possible to return changed values from the function. If there is only one return value, this is most simply done by using the return statement:

func2.ox
#include <oxstd.oxh>
func(const r, const c)
{
    return rann(r, c);      // return r x c matrix of random
}                            // numbers from standard normal
main()
{
    print("return value from func():", func(1,2) );
}

Another way is to pass a reference to the variable, rather than the variable itself, as for example in:

func3.ox
#include <oxstd.oxh>
func(const pmA)
{
    pmA[0] = zeros(1,2);
    print("ma in func()", pmA[0]);
}
main()
{
    decl ma = ones(1,2);
    print("ma before func()", ma);
    func(&ma);
    print("ma after func()", ma);
}

which prints:

ma before func()
       1.0000       1.0000
ma in func()
      0.00000      0.00000
ma after func()
      0.00000      0.00000

Now the change to ma is permanent. The argument to the function was the address of ma, and func received that address as a reference. Now we can modify the contents of the reference by assigning a value to pmA[0]. When func has finished, ma has been changed permanently. Note that we gave the argument a const qualification. This was possible because we did not change pmA itself, but what it referred to.

Many library functions return results this way because early versions of Ox did not have the more convenient mechanism of multiple returns:

func4.ox
#include <oxstd.oxh>
func()
{
    return { "text", 99, <10,12> };
}
main()
{
    decl [a, b] = func();
    println("a=", a);
    println("b=", b);
    [a, , b] = func();
    println("now b=", "%v", b);
}

which prints:

a=text
b=99
now b=<10,12>

Here we combined multiple returns with multiple assignment. The function returned three values in an array, but the last one was ignored in the assignment.

It is also possible to skip the middle value (say), as the final line in the example shows.

The for and while loops

Since Ox is a matrix language, there is not so much need for loop statements. Indeed, because Ox is compiled and then interpreted, there is a speed penalty for using loop statements when they are not necessary.

The for, while and do while loops have the same syntax as in languages like C and Javascript. The for loop consists of three parts, an initialization part, a termination check, and an incrementation part. The while loops only have a termination check.

for.ox
#include <oxstd.oxh>
main()
{
    decl i, d;
    for (i = 0; i < 5; ++i)
    {
        d = i * 0.01;
        println(d);
    }
}

which prints:

    0
    0.01
    0.02
    0.03
    0.04

This could also be written, less elegantly, using while as follows:

while.ox
#include <oxstd.oxh>
main()
{
    decl i, d;
    i = 0;
    while (i < 5)
    {
        d = i * 0.01;
        println(d);
        ++i;
    }
}

The declaration of the loop counter can be moved in to the for statement, restricting its scope:

for2.ox
#include <oxstd.oxh>
main()
{
    for (decl i = 0; i < 5; ++i)
    {
        decl d = i * 0.01;
        println(d);
    }
    // i does not exist here
}

It is not uncommon to have more than one loop counter in the for statement, as the following code snippet illustrates:

    for (decl i = 0, j = 10; i < 5 && j > 0; ++i, --j)
        println(i * j);

The && is logical-and, whereas || is logical-or. The ++i statement is called (prefix) incrementation, and means `add one to i'. Similarly, --j subtracts one from j. There is a difference between prefix and postfix incrementation (decrementation). For example, the second line in

    i = 3;
    j = ++i;

means: add one to i, and assign the result to j, which will get the value 4. But

    i = 3;
    j = i++;

means: leave the value of i on the stack for assignment, then afterwards increment i. So j will get the value 3. In the incrementation part of the for loop it does not matter whether you use the prefix or postfix form.

The foreach statement

The foreach loop is a convenient way to loop over the elements of an array or matrix, without the need to `count' the number of elements:

foreach.ox
#include <oxstd.oxh> main() { decl as = {"AA", "BB"}; foreach (decl s in as) { print(" ", s); } }

which prints AA BB. Similarly, foreach (xi in mx) loops over each element in mx. The element variable (xi here), must be a local variable, while the collection (mx) can be any pre-existing variable:

foreach2.ox
#include <oxstd.oxh> main() { decl xi, mx = <1,2,3;4,5,6;7,8,9>; foreach (xi in mx) { print(xi); } }

The elements are accessed element-by-element, ordered by row, so this prints: 123456789. Sometimes it is useful to access the matrix by entire rows or columns. Or to have access to the iterator. Both are possible:

foreach3.ox
#include <oxstd.oxh> main() { decl xi, mx = <1,2;3,4>, i, j, vx = vec(mx); foreach (xi in mx[i][j]) { println("element ", i, ",", j, ": ", xi); } foreach (xi in mx[i][]) { println("row ", i, ": ", xi); } foreach (xi in mx[][j]) { println("column ", j, ": ", xi); } foreach (xi in vx[i]) { println("vector element ", i, ": ", xi); } }

This prints:

element 0,0: 1 element 0,1: 2 element 1,0: 3 element 1,1: 4 row 0: 1.0000 2.0000 row 1: 3.0000 4.0000 column 0: 1.0000 3.0000 column 1: 2.0000 4.0000 vector element 0: 1 vector element 1: 3 vector element 2: 2 vector element 3: 4 Note that the dimension of the matrix is not allowed to change during the loop.

The if statement

The if statement allows for conditional program flow. In the following example we draw a uniform random number. Such a random number is always between zero and one. The ranu returns a matrix, unless we ask it to generate just one number. Then it returns a double, as is the case here.

    decl d = ranu(1,1);
    if (d < 0.5)
        println("less than 0.5");
    else if (d < 0.75)
        println("less than 0.75");
    else
        println("greater than 0.75");

Again, braces are needed to group multiple statements together. They should also be used when nesting if statements, to avoid confusion about which else belongs to which if.

    decl d1 = ranu(1,1), d2 = ranu(1,1);
    if (d1 < 0.5)
    {   
        println("d1 is less than 0.5");
    }
    else
    {   
        if (d2 < 0.75)
            println("d1 >= 0.5 and d2 < 0.75");
        else
            println("d1 >= 0.5 and d2 <= 0.75");
    }

The if part is executed if the expression evaluates to a non-zero value (true). The else part otherwise, i.e. when the expression evaluates to zero (false: either an integer 0, or a double 0.0). Some care is required when using matrices in if statements. A matrix expression is a true statement if all elements are true (non-zero). Even if only one element is zero, the matrix expression is false, so

if.ox
#include <oxstd.oxh>
main()
{
    if (ones(2,2))  print("yes");
    else            print("no");
    if (unit(2))    print("yes");
    else            print("no");
    if (zeros(2,2)) print("yes");
    else            print("no");
}

prints: yesnono.

There are two forms of relational operators. The first group cnsists of < <= > >= == != meaning `less', `less than or equal', `greater', `greater than or equal', `is equal' and `is not equal'. These always produce the integer value 1 (true) or 0 (false). If any of the arguments is a matrix, the result is only true if it is true for each element:

if2.ox
#include <oxstd.oxh>
main()
{
    if (ones(2,2) == 1)  print("yes");   // true for each
    else                 print("no");          // element
    if (unit(2) == 1)    print("yes");//not true for each
    else                 print("no");          // element
    if (zeros(2,2) == 1) print("yes");//not true for each
    else                 print("no");          // element
}

prints: yesnono.

The second group contains the dot-relational operators .< .<= .> .>= .== .!= meaning `dot less', `dot less than or equal', `dot greater', `dot greater than or equal', `is dot equal' and `is not dot equal'. If any of the arguments is a matrix, the result is a matrix of zeros and ones, with each element indicating the relevant result.

The any library function returns 1 (true) if any element of the matrix is non-zero, so that yesyesno will be printed by:

if3.ox
#include <oxstd.oxh>
main()
{
    if (any(ones(2,2)))  print("yes");
    else                 print("no");
    if (any(unit(2)))    print("yes");
    else                 print("no");
    if (any(zeros(2,2))) print("yes");
    else                 print("no");
}

To conclude: you can test whether all elements of a matrix m are equal to one (say) by writing: if (m == 1). To test whether any element is equal to one: if (any(m .== 1)). The expression if (m != 1), on the other hand, is only true if none of the elements is equal to one. So, use if (!(m == 1)) to test whether it is true that not all elements are equal to one.

Operations and matrix programming

To a large extent, the same operators are available in Ox as in C, C++, or Javascript. Some of the additional operators are power (^), horizontal concatenation (~), vertical concatenation (|) and the Kronecker product (**). One important distinction is that the operators are also available for matrices, so that, for example, two matrices can be added up directly. For some operators, such as multiplication, there is a distinction between the dot operators (e.g. .* is element by element multiplication and * is matrix multiplication if both arguments are matrices). Not available in Ox are the bitwise operators, instead you need to use the library functions binand and binor.

Because Ox is implicitly typed, the resulting type of the expression will depend on the types of the variables in the expression. When a mixture of types is involved, the result is promoted upwards in the order integer, double, matrix. So in an expression consisting if an integer and a double, the integer will be promoted to a double. An expression of only integers yields an integer. However, there are two important exceptions to this rule:

  1. integer division is done in floating point and yields a double. This is an important difference with C, where integer division is truncated to an integer.
  2. power expressions involving integers which yield a result too large to be expressed as an integer give a double result.

To illustrate, we write the Fahrenheit to Celsius example of Kernighan and Ritchie (1988) in Ox:

celsius1.ox
#include <oxstd.oxh>
const decl LOWER = 0;
const decl UPPER = 100;
const decl STEP  = 20;
main()
{
    decl fahr;
    for (fahr = LOWER; fahr <= UPPER; fahr += STEP)
        println("%3d", fahr, " ",
              "%6.1f", (5.0/9.0) * (fahr-32));
}

which prints:

      0  -17.8
     20   -6.7
     40    4.4
     60   15.6
     80   26.7
    100   37.8

In C we have to write 5.0/9.0, because 5/9 evaluates to zero. In Ox both expressions are evaluated in floating point arithmetic.

In general we get more more efficient code by vectorizing each program as much as possible:

celsius2.ox
#include <oxstd.oxh>
const decl LOWER = 0;
const decl UPPER = 100;
const decl STEP  = 20;
main()
{
    decl fahr = range(LOWER, UPPER, STEP)';
    println("%cf", {"%7.0f","%7.1f"}, fahr ~ (5.0/9.0) * (fahr-32) );
}

The program prints a table similar to the earlier output:

      0  -17.8
     20   -6.7
     40    4.4
     60   15.6
     80   26.7
    100   37.8

Arrays

The Ox syntax allows for arrays, so you may use, for example, an array of strings (often useful), an array of matrices, or even an array of an array of matrices (etc.). The following program gives an example.

array.ox
#include <oxstd.oxh>
main()
{
    decl cr = 2, cc = 3;
    decl asr = new array[cr];
    decl asc = new array[cc];
    for (decl i = 0; i < cr; ++i)
        asr[i] = sprint("row ", i);
    for (decl i = 0; i < cc; ++i)
        asc[i] = sprint("col ", i);

    decl m = ranu(cr, cc);
    print("%r", asr, "%c", asc, m);

    // or much more compactly:
    print("%r", "row " * range(0, sizer(m) - 1, "s"),
          "%c", "col " * range(0, sizec(m) - 1, "s"), m);
}

which prints twice

                  col 0        col 1        col 2
    row 0       0.56444      0.76994      0.41641
    row 1       0.15881     0.098209      0.37477

Multiple files: using #include and #import

The source code of larger projects will often be spread over several source files. Usually the .ox file containing the main function is only a few tens of lines. We have already seen that information about other source files is passed on through included header files. However, to run the entire program, the code of those files needs to be linked together as well. Ox offers various ways of doing this. As an example, consider a mini-project consisting of two files: a source code file and a header file. The third file will contain the main function.

Mini project [samples/myfunc.ox]

#include <oxstd.oxh>
static decl s_iCalls = 0;  // counter, initialize to 0
MyFunction(const ma)
{
    ++s_iCalls;             // increment calls counter
    println("MyFunction has been called ", s_iCalls,
          " times and prints:", ma);
}

[samples/myfunc.h]

    MyFunction(const ma);

The header file myfunc.h declares the MyFunction function, so that it can be used in other Ox files. Note that the declaration ends in a semicolon. The source code file contains the definition of the function, which is the actual code of the function. The header of the definition does not end in a semicolon, but is followed by the opening brace of the body of the function. The s_iCalls variable is declared outside any function, making it an external variable. Here we also use the static type specifier, which restricts the scope of the variable to the myfunc.ox file: s_iCalls is invisible anywhere else (and other files may contain their own s_iCalls variable). Variables declared inside a block of curly braces have a more limited lifetime. Their life starts when they are declared, and finishes at the closing brace (matching the brace level of declaration).

Note that a function like MyFunction has side effects (i.e. is not self contained), so cannot be used in a parallel loop without marking it as serial.

It is also possible to share variables between various source files, although there can be only one declaration (physical allocation) of the shared variable. The following modifications would do that for the myfunc.ox program:
(1) delete the static keyword from the declaration,
(2) add to myfunc.h the line (renaming s_iCalls to g_iCalls):

    extern decl g_iCalls;

Any code which includes myfunc.h can now reference or change the g_iCalls variable.

Including the code into the main file

The first way of combining the mini project with the main function is to #include the actual code. In that case the myfunc.h header file is not needed:

[samples/mymaina.ox]

#include <oxstd.oxh>
#include "myfunc.ox"
main()
{
    MyFunction("one");
}

The result will be just one code file, and mymaina.ox can be run as oxl mymaina.

Importing the code into the main file

The drawback of the previous method of including source code using #include, is that it is only allowed to occur once. That is not a problem in this short program, but is difficult to ensure if a library is used at many points in a large project. The #import command solves this problem.

[samples/mymainc.ox]

#include <oxstd.oxh>
#import "myfunc"
main()
{
    MyFunction("one");
}

Again, mymainc.ox can be run as oxl mymainc. There is no extension in the argument to #import. The effect is as an #include "myfunc.h" statement followed by marking myfunc.ox for linking.
[ #import will actually try to find the .oxo file first. If that is not found, it will search for the .ox file. If neither is found, the program cannot run. More detail is in import of modules. ]
The actual linking only happens when the file is run, and the same #import statement may occur multiple times (as well as in compiled files). So even when the same file is imported many times, it will only be linked once.

Importing Ox packages

If myfunc.ox would require the maximization package, it could have at the top:

#include <oxstd.h> #import <maximize>

Partial paths can be used. Searching is relative to the path that holds the Ox executable, as well as the oxmetrics10/ox folder in your user Documents folder. For example, if the Arfima package is in its default location of ox/packages/arfima, we would use:

#import <packages/arfima/arfima>

The distinction between angular brackets and double quotes in the include and import statements is discussed in file inclusion. Roughly, the <> form should be used for files which are part of the Ox system, and the double quotes for your own files, which will not be in the Ox tree.

Separate compilation

Ox source code files can be compiled into Ox object files. These files have the .oxo extension, and are binary. The format is identical across operating systems, but since they are binary, transfer from one platform to another has to be done in binary mode.

To compile myfunc.ox into an Ox object file with Ox-compile from OxEdit or OxMetrics' Run menu. Or use the -c switch on the command line:

    oxl -c myfunc

This creates myfunc.oxo (the .oxo extension is automatically appended). Remember that myfunc.oxo must be recreated every time myfunc.ox changes.

Now, when rerunning mymainc.ox, it will automatically use the .oxo instead of the .ox file.

Compiled Ox files can be useful for very large files (although even then compilation will be very fast), or if you do not wish to distribute the source files. They are inconvenient when the code is still under development.

Object-oriented programming

Object-oriented programming involves the grouping together of functions and variables in convenient building blocks. These blocks can then be used directly, or as starting point for a more specialized implementation. A major advantage of object-oriented programming is that it avoids the use of global variables, thus making the code more re-entrant: several instances will not conflict wiith each other.

The object-oriented features in Ox are not as sophisticated as in some low-level languages. However, this avoids the complexity of a language such as C++, while still providing most of the benefits.

Ox allows you to completely ignore the object-oriented features. However, you will then not be able to use the preprogrammed classes for data management and simulation. It is especially in the latter task that we found a considerable reduction in the required programming effort after writing the base class.

The class is the main vehicle for object-oriented programming. A class is nothing more than a group of variables (the data) and functions (the actions) packaged together. This makes it a supercharged struct (or record in Pascal terminology). Inheritance allows for a new class to add data and functions to the base class, or even redefine functionality of the base class.

In Ox, the default is that all data members of the class are protected (only visible to class members), and all function members are public. Ox has the virtual keyword to define functions which can be replaced by the derived class. Classes are used by dynamically creating objects of that class. No static objects exist in Ox. When an object is created, the constructor function is called, when the object is deleted, the destructor function is called. More information on object-oriented programming is given in the Introduction to Ox and syntax chapter.

Style and Hungarian notation

The readability and maintainability of a program is considerably enhanced when using a consistent style and notation, together with proper indentation and documentation. Style is a personal matter; this section describes the one I have adopted.

In my code, I always indent by one tab (four spaces) at the next level of control (i.e. after each opening brace), jumping back on the closing brace.

Table tut.1:  Hungarian notation prefixes

prefix type example
i integer iX
c count of cX
b boolean (f is also used) bX
fl integer flag flX
d double dX
m matrix mX
v vector (1 ×n or n ×1 matrix) vX
s string sX
as array of strings asX
am array of matrices amX
a reference in function argument amX
m_ class member variable m_mX
s_ static external variable (file scope) s_mX
g_ external variable with global scope g_mX
fn function reference fnX

I have found Hungarian notation especially useful (see e.g. Petzold, 1992, Ch. 1). Hungarian notation involves the decoration of variable names. There are two elements to Hungarian notation: prefixing of variable names to indicate type (Table Table 1), and using case to indicate scope (Table Table 2, remember that Ox is case sensitive).

Table tut.1:  Hungarian notation, case sensitivity

function all lowercase
function (exported) first letter uppercase
static external variable type in lowercase, next letter uppercase
 (perhaps prefixed with s_)
exported external variable as above, but prefixed with g_
function argument type in lowercase, next letter uppercase
local variables all lowercase
constants all uppercase

As an example consider:

    #include <oxstd.oxh>
    const decl MX_R = 2;                      /* a constant */
    decl g_mX;                           /* exported matrix */
    static decl s_iCount;       /* static external variable */
    static func1(const pdX)/* argument is pointer to double */
    {
    }
                                       /* exported function */
    Func2(const mX, const asX, const cT, const cX)
    {
        decl i, m;
    }

Func2 expects a cT × cX matrix, and corresponding array of cX variable names. The c prefix is used for the number of elements in a matrix or string. Note however, that it is not necessary in Ox to pass dimensions separately. You can ask mX and asX what dimensions they have:

    Func2(const mX, const asX)
    {
        decl i, m, ct, cx;
        cx = columns(mX);
        ct = rows(mX);
        if (cx != sizeof(asX))
            print("error: dimensions don't match");
    }

Optimizing for speed

Ox is very fast: current benchmarks suggest that it is faster than most (if not all) other commonly used matrix language interpreters. A program can never be fast enough though, and here are some tips to achieve even higher speed:

Conclusions

After this quick overview of Ox, it is recommended that you continue with the Introduction to ox.

Next there is the Ox reference to explore.

References

Doornik, J.A. and Ooms, M. (2026). Introduction to Ox, 3rd ed., London: Timberlake Consultants Press.

Kernighan, B.W. and Ritchie, D.M. (1988). The C Programming Language 2nd Ed. Englewood Cliffs, NJ: Prentice Hall.

Petzold, C. (1992). Programming Windows 3.1. Redmond: Microsoft Press.


Ox version 10.0. © JA Doornik This file last changed .