Names and modules handling

Starting today Argentum got new rules for modules and name visibility.

TL;DR

Previously:

  • The using directive worked much like include (but with protection from multiple includes and loops).
  • Names were disconnected from module names and made their own hierarchy, like namespaces in C++/C#.

Now:

  1. Each using directive defines a dependency between modules.
  2. Using-dependencies are not transitive (If "A using B" and "B using C", then A cannot directly access names from C without explicit declaration on "A using C").
  3. The order of using directives has no effect on the compilation result.
  4. In theory modules could be compiled independently.
  5. Each module defines its own space for names.
  6. From inside the module all elements of the same module can be referred by short names.
  7. Content of other modules is accessible with long names: moduleName_classOrFunctionName.
  8. "Using" directives allow to import names from other modules, so they could be used as local names without module prefix: using gui { Scene } - this adds dependency to module gui, and imports name Scene from it.
  9. Imported names can be renamed to evade collisions using gui { Sc = Scene }.
  10. Imported modules can also be renamed for readability.
  11. There is a sys module that needs not to be imported, but it may if its internals needs to be accessed without sys_ prefix.

Examples

All examples can be found in windows-demo

#1 Basics

using sys { String, StrBuilder, log }   // 1
using string;                           // 2
using utils{ forRange }                 // 3

fn itos(i int) @String {                             // 4
    StrBuilder.putInt(i).putCh(0x0a).toStr()         // 5
}
forRange(1, 101, (i){                                // 6
    log(i % 5 == 0                                   // 7
        ? (i % 3 == 0 ? "fizzbuzz\n" : "fizz\n")
        : (i % 3 == 0 ? "buzz\n" : itos(i)))         // 8
})
  • In lines 1..3 we import class names String and Builder and function names log, forRange from their correspondent modules.
  • In line {4} we define a itos function that we later call in line {8}.
  • If we would decide to use this function from the other module, we could add there: using fizzBuzz{ itos }
  • In lines {6} and {7} we call functions from other modules with short names.
  • In lines {4} and {5} we refer to the classes from other modules.

#2 Friend of my friend...

The SDL-FFI module contains the low-level function declarations implemented in C:

using sys { String, Blob }
// Low level unsafe API
fn sdlInit(int flags) int;
fn sdlQuit();
fn createWindow(title String, x int, y int, w int, h int, flags int) int;
...

The SDL module exposes the high-level OOP API build atop od SDL-FFI:

using sys { String }
using sdlFfi;

class Sdl {
    initVideo() bool {
        sdlFfi_sdlInit(0x7231) == 0 && // SDL_INIT_EVERYTHING
        sdlFfi_imgInit(3) != 0         // JPG, PNG
    }
    eventLoop(handler (Event)void) {
        SdlQuit = 256;
        e = Event;
        loop{
            sdlFfi_pollEvent(e);
            handler(e);
            sdlFfi_delay(calcFrameMs(60));
...

The main application uses SDL module, but doesn't have access to SDL-FFI:

using sdl { Window, Sdl, Texture }
using utils { Random, forRange }
using gui { Actor, Group }

class Scene{
    +Group;

    sdl = Sdl;
    w = Window;
    rnd = Random;
    flare = Texture;
...

This example demonstrate how to build layering modules that hide the details of implementation and reduce complexity.

Modules and class extension

Argentum allows to extend classes of one module from other modules:

using sys { Object, WeakArray }
class WeakArray{
    size() int { capacity(this) }
    ...
}

In this example module array adds to the sys_WeakArray class multiple extra methods like size.

using sys { WeakArray }
using utils { existsInRange }
using array;                                         // 1

class Node {
    connections = WeakArray(Node);
    hasLoop() bool {
       existsInRange(0, connections.size(), (i){     // 2
           connections[i] && _.hasLoop()
       });
    }
...

Main application imports both sys and array module in line {1}.
This allows to call size method in line {2}.
If for some reason two different modules happened to extend some class with fields or methods having the same name, they can be distinguished by adding a module names: connections.array_size().

Conclusion

The new rules for handling module names offer several benefits:

  • Eliminating dependencies on the order of inclusion
  • Improving program self-documentation: all external names are either declared at the top of the module or explicitly marked with the module of origin at the place of usage
  • Enhancing program readability: modules local names are always short, redundant long names can be imported, and long module names can be aliased
  • Preventing exposure of module implementations or internal dependencies and reducing coupling
  • Defining names by modular structure, rather than by a parallel namespace structure

This also opens up the potential ability to compile modules separately.

Leave a Reply

Your email address will not be published. Required fields are marked *