PreviousUpNext

5.3.15  Multi-file Projects: Libraries and API Definitions

One-file scripts are great for little system administration tasks and the like, but eventually you will want to write a serious application in Mythryl, and at that point you need to be able to divide the source code up logically between multiple files with cleanly defined interfaces between them. It is time we examined how to do that in Mythryl.

First we need to talk a little bit about API definition. Critical to the construction of any large software project is the concept of implementation hiding, of dividing the code up into modules which each expose to the external world only a simple, clean, well-defined interface. Code within each module can then be freely modified as long as the interface remains unchanged. This makes maintenance and evolution of large software systems enormously easier.

In Mythryl we define such module interfaces using the api construct.

Suppose for example that we want to create a library of functions to do useful things with Mythryl lists. We will call the library list_lib and in it we will define a function list_length which accepts a list of strings and returns the length of that list as an integer. This library will have various internal functions, but the only function it will export to the external world will be list_length.

We will implement this by writing an API which we will call List_Lib, which will define the external interface to our library:

    api List_Lib {
        list_length:  List(String) -> Int;
    };

This says that the List_Lib api makes available to the external world a single function named list_length which accepts a list of strings and returns an integer.

Now we implement our library package proper:

    package list_lib: List_Lib {

        # Private helper function for computing
        # list length.  Its second argument
        # counts the number of list elements
        # seen so far.  This is a common MythryL idiom:
        #
        fun length_helper (rest_of_list, nodes_seen)
            =
            if (rest_of_list == [])    nodes_seen;                                              # Done, return count. 
            else                       length_helper( tail(rest_of_list), nodes_seen + 1 );     # Count rest of list recursively.
            fi;

        fun list_length a_list
            =
            length_helper (a_list, 0);
    };

The package list_lib: List_Lib looks like a package API declaration, but in fact it is a package cast which forces the external view of the list_lib package to be exactly that specified by the List_Lib API definition.

Here the length_helper helper function does all the real work, but it is not externally visible because we did not list it in our List_Lib api definition. This is a simple example of implementation hiding.

The list_length function is really just a wrapper around length_helper, but it is the only externally visible part of the package.

We can test our package and api using a little script:

    #!/usr/bin/mythryl

    api List_Lib {
        list_length:  List(String) -> Int;
    };

    package list_lib: List_Lib {

        # Private helper function for computing
        # list length.  Its second argument
        # counts the number of list elements
        # seen so far.  This is a common MythryL idiom:
        #
        fun length_helper (rest_of_list, nodes_seen)
            =
            if (rest_of_list == [])    nodes_seen;                                              # Done, return count. 
            else                       length_helper( tail(rest_of_list), nodes_seen + 1 );     # Count rest of list recursively.
            fi;

        fun list_length a_list
            =
            length_helper (a_list, 0);
    };

    printf "list length is %d\n"  (list_lib::list_length( ["abc", "def", "ghi"] ));

When run, this yields

    linux$ ./my-script
    list length is 3
    linux$ 

But declaring an api and package within a script file was not the point; the point was to compile multi-file applications. To do that, save the package definition in a file named list-lib.pkg, the api declaration in a file named list-lib.api and create a file named list-lib.lib to control their compilation with contents as shown below, and compile the complete fileset interactively as shown:

    linux$ cat list-lib.api

    api List_Lib {
        list_length:  List(String) -> Int;
    };

    linux$ cat list-lib.pkg

    package list_lib: List_Lib {

        # Private helper function for computing
        # list length.  Its second argument
        # counts the number of list elements
        # seen so far.  This is a common MythryL idiom:
        #
        fun length_helper (rest_of_list, nodes_seen)
            =
            if (rest_of_list == [])    nodes_seen;                                              # Done, return count. 
            else                       length_helper( tail(rest_of_list), nodes_seen + 1 );     # Count rest of list recursively.
            fi;

        fun list_length a_list
            =
            length_helper (a_list, 0);
    };

    linux$ cat list-lib.lib

    LIBRARY_EXPORTS

            api List_Lib
            pkg list_lib

    LIBRARY_COMPONENTS

            $ROOT/src/lib/std/standard.lib

            list-lib.api
            list-lib.pkg


    linux$ my

    eval:  make "list-lib.lib";
        src/app/makelib/main/makelib-g.pkg:   Running   .lib file    list-lib.lib
          parse/libfile-parser-g.pkg:   Reading   make   file   list-lib.lib                                          on behalf of <toplevel>
    .../compile/compile-in-dependency-order-g.pkg:   Loading                 list-lib.api
    .../compile/compile-in-dependency-order-g.pkg:   Loading                 list-lib.pkg
        src/app/makelib/main/makelib-g.pkg:   New names added.

    TRUE

    eval:  makelib::show_all();

    Top-level definitions:
    [...]
    api List_Lib
    pkg list_lib
    val it

    ()

    eval:  list_lib::list_length( ["abc", "def", "ghi"] );

    3

Here we used make "list-lib.lib"; to compile and load our library, then we used makelib::show_all(); to list all loaded packages and apis, verifying that List_Lib and list_lib were now present, and then invoked our library by evaluating list_lib::list_length( ["abc", "def", "ghi"] );, verifying that it returned the expected value of three.

The list-lib.lib file contents should be reasonably self-explanatory.

The LIBRARY_COMPONENTS section lists all source files which should be compiled to form the library, together with all the sub-libraries needed by those source files. You will probably always want to list the standard Mythryl libraries $ROOT/src/lib/std/standard.lib in this section; if you have additional sub-libraries of your own needed by the source files in this library, you will need to list them as well.

The LIBRARY_EXPORTS section lists all of the apis and packages which should be made externally visible to users of your package. Just as a package may have internal functions which are not made externally visible, so a complex library may have entire packages which are for internal use only and not made externally visible.

We shall have more to say about Mythryl libraries later but right now it is time to learn how to compile stand-alone executables.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext