Ch.11 Separate compilation in SML#

§ 11.2. Separate compilation example

Following the simple scenario in the previous section, let us develop an Omikuji (Japanese written oracle drawing) program as an example. Instead of asking Japanese sacred split, we use extremely good random number generator. The system is divided into

  • random: a random number generator, and

  • main: the main part.

The fist step is to design interface files as follows.

  • random.smi:

    structure Random =
    struct
      val intit : int -> unit
      val genrand : unit -> int
    end

    This interface file says that the implementing source file provide Random structure without using any other files.

  • main.smi:

    _require "basis.smi"
    _require "./random.smi"

    This interface file says that it uses "basis.smi" (The Standard ML Basis Library) and random.smi, and provide no resource.

Using these interface files, main.sml and random.sml are developed independently. main.sml can be given as in Figure 11.1. This file can be compiled without any source file that implements random.smi, and checks syntax and type errors by typing:

$ smlsharp -c main.sml

The -c switch instructs SML# compiler to compile the specified source file to an object file. The compiler checks its syntax and types and if no error is detected then it produces main.o file. main.smi is chosen as its interface file by default. A specific interface file can be specified by including the directive _interface filePath at the beginning of the source file.

Next,we develop random.sml. Development of a high quality random number generator requires expert knowledge in algebraic number theory and careful coding. Here, instead of developing one from scratch, we try to find some existing quality code. Among a large number of implementations, perhaps Mersenne Twister is the best in its quality and efficiency. So we decide to use this algorithm, which is available as a C source file mt19937ar.c.

Let us obtain the C source from the Internet by searching for Mersenne Twister or mt19937ar.c. The file contains the following function definitions.

void init_genrand(unsigned long s)
void init_by_array(unsigned long init_key[], int key_length)
unsigned long genrand_int32(void)
long genrand_int31(void)
double genrand_real1(void)
double genrand_real2(void)
double genrand_real3(void)
double genrand_res53(void)
int main(void)

Among thme, main is a main function that test the algorithm. Since we are defining our executable, the main function should be in the compiled object file of our top level source file main.sml. So we comment out int main(void) function in the file. mt19937ar.c. All the others functions can be used from our program. Here we decide to use the following two.

  • void init_genrand(unsigned long s) for initializing the algorithm with the seed length s, which can be any non-negative integer.

  • long genrand_int31(void) for generating 31 bit unsigned (i.e. 32 bit non-negative) random number sequence.

The random.sml can then be defined as the following code that simply call these functions.

structure Random =
struct
  val init = _import "init_genrand" : int -> unit
  val genrand = _import "genrand_int31" : unit -> int
end

This source file can be compiled independently of other files by the following command.

$ smlsharp -c random.sml

In doing this, we compile Mersenne Twister (aftre commenting out its main function).

$ gcc -c -o mt.o mt19937ar.c

We now have all the object files for the program. We can link them to an executable file by specifying the top-level interface file and the external object files referenced through _import declarations.

$ smlsharp main.smi mt.o

SML# analyzes smi file,traverse all the interface files referenced from this file, make a list of object files corresponding to the interface files, and then links all the object files with those specified in the command line argument to generate an executable file.

fun main() =
  let
    fun getInt () =
      case TextIO.inputLine TextIO.stdIn of
        NONE => 0
      | SOME s => (case Int.fromString s of NONE => 0 | SOME i => i)
    val seed = (print "input a number of your choice (0 for exit)"; getInt())
  in
    if seed = 0 then ()
    else
      let
        val _ = Random.init seed;
        val oracle = Random.genrand()
        val message =
          " あなたの運勢は," ^
          (case oracle mod 4 of 0 => "大吉" | 1 => "小吉"| 2 => "吉" | 3 => "凶")
          ^ "です.\n"
        val message = print message
      in
        main ()
      end
  end
val _ = main();
Figure 11.1. Example of main.sml