Kaya modules

K

Writing Kaya modules

Using modules

The Kaya module system is used to include library functions, and to split source code into manageable files. Importing modules is done by placing import modulename; at the top of the importing file. To make dependency analysis (see below) efficient, all import statements should be at the top of the file, on separate lines, and with no leading whitespace.


// correct
import Strings;
import Dict;
import Regex;

// may cause problems
import Strings; import Dict;
                import Regex;

Unless you use the -noprelude option when compiling, the ArrayBuiltinsCoercionsMathsPrelude and Tuples modules will be automatically imported. Generally this option is only used when compiling the standard library itself.

Creating modules

To create a module, start the source file with module modulename;. Generally, the filename of the source file should be modulename.k to allow dependency chasing to work properly. This ensures that all necessary modules will be (re)compiled in the correct order.

A module may import modules itself; however, if module A imports module B, then anything that imports module A will not automatically have access to data and functions from module B. If this is necessary (for example, if module A defines a data type in terms of a data type from module B), then use import public modulename; in module A to import module B.

Using public, private and abstract components

Functions in modules may be either public, so that they can be called by functions outside the module, or private, which makes them only usable within the module. The default is ‘private’. It is often best to keep as much of the module internals private as possible, to make it easy to re-implement the module without affecting the public interface.

Data types in modules may be public or private, but may also be abstract. This allows other modules to use the data type, but not to have access to its constituent parts. For example, the Dict module defines its eponymous data type as:

abstract data Dict = Dict([[(a,b)]] entries, Int buckets, Int(a) hashfn);

If this was instead defined as public, then it would be possible to accidentally modify the contents of a dictionary without using the provided interface, and so corrupt the data with unpredictable results.

Example module

module StringReverse;

import Strings;

public String reverse(String unencoded) {
    encoded = "";
    for x in [0..length(unencoded)] {
        encoded = substr(unencoded,x,1) + encoded;
    }
    return encoded;
}

If this module was saved as StringReverse.k, it could then be used in programs:

program test;

import StringReverse;

Void main() {
    test = "abcde";
    rtest = reverse(test);
    putStrLn(rtest); 
}

Assuming StringReverse.k and the test.k program are in the same directory:

$ kayac test.k
Compiling module StringReverse
Compiling program test
$ ./test
edcba
$

Module namespaces

If two functions have the same name and type, they can still be used in the same program provided they come from different modules, by prefixing the name of the function with the name of the module.

program test;

import StringReverse;
import OtherModule; // also has a String(String) reverse function

Void main() {
    test = "abcde";
    rtest = StringReverse::reverse(test);
    putStrLn(rtest); 
}

If two functions have the same name, but different argument types, the compiler can identify which function is meant from the usage. You can use module:: to refer to the current module.

Data types also live in per-module namespaces and can be disambiguated in the same way as functions.

Global variables

Global variables are evil, but sometimes they’re very useful indeed. In Kaya, global variables are per-module only (that is, they can’t be exported), and must all be declared together in a globals block, as follows:

globals {
    <declarations>
}

Only one globals block is allowed per module, which is intended to keep things neat and save variable chasing.

There is no technical reason why we have to have this globals block, rather than scattering globals around a source file liberally. This way is preferred, however, because it draws attention to the fact that there are globals in a module, and hopefully discourages their unnecessary use.

Initial values of global variables

Globals may be given initial values at program startup. The order of global initialisation is undefined.

import Time;
import Dict;

globals {
    Int start = time(); // initialised automatically
    Dict<String,String> options = Dict::new(); // likewise
    Int end; // starts uninitialised
}

In the CGI and Webapp programming models (see the web tutorials), global initialisation takes place before any user-supplied data is read.

Circular dependencies

Circular dependencies are not currently allowed in Kaya – if module A imports module B, then module B must not import module A or neither module will compile. However, external function declarations are available and should allow all requirements for circular dependencies to be bypassed.

External function declarations

It is sometimes necessary, for example when creating templates in web applications, to refer to a function in one module that exists in a second module that imports the first module. To do this, an external function declaration can be used – a promise to the compiler that any program importing the first module will also import the second module.

module A;
import B;
// for reasons not shown in the example, module A must import module B

public Int sampleFunction(Int a, Int b) {
  return a+b;
}
module B;
// we need to call A::sampleFunction from this module, perhaps as
// a partial function for a webapp, but because A imports B, B may 
// not import A

extern public Int sampleFunction(Int a, Int b);

There are a few rules that must be followed when using external function declarations to ensure that your programs work as expected.

  • The function must be declared as ‘public’ in module A, and as ‘extern public’ in module B.
  • The function name, return types and parameter types must match exactly.
  • Any module or program that imports one of module A or module B must now import both of them.

It is best to avoid external function declarations where it is possible to do so without sacrificing the maintainability of your code.

External type or Exception declarations are not possible, but at the moment we believe these should never be necessary – please get in touch if you have a good counter-example for us.

Recent Comments

No comments to show.

Pages