Proposal: Require Namespaces - adamnew123456/spc GitHub Wiki

Require Namespace

To avoid namespace collisions, and increase each module's control over its namespace, each require statement will be required to be of the form (require IDENTIFIER STRING). The identifier in this statement is the require namespace, and it must be unique in each file.

Namespace Access

Accessing any identifier in the required module will require prefixing the identifier with a namespace prefix, which is denoted as IDENTIFIER:, where the identifier is the same as the identifier from the require statement.

For example, this would require the following code:

(require "lib/io.lisp")
(declare (hello-world (ascii "Hello, World!\n"))
         (main (function byte)))

(define main ()
 (declare)
 (println hello-world))

To be modified into code that looks like this:

(require io "lib/io.lisp")
(declare (hello-world (ascii "Hello, World!\n"))
         (main (function byte)))

(define main ()
 (declare)
 (io:println hello-world))

Note that resolution is context-dependent, like in the rest of the language. a:x could be a type or a value, depending upon whether it was given as an argument to size-of or +.

Recursive Namespaces

Recursive namespaces, which are instances where a module exports a namespace that it has imported, are allowed. Doing this requires the implementation of another proposal, Revamped Exports.

Following that proposal, namespaces would be identified with the prefix :, and can be re-exported like any values and types. Like values and types, they will end up in the namespace of the required module, creating a recursive namespace:

; a.lisp
(require b "b.lisp")
(declare (main (function byte)))

(define main ()
 (declare) 
 (return (b:c:function)))

; b.lisp
(require c "c.lisp")
(export :c)

; c.lisp
(declare (function (function byte)))
(export 'function)

(define function () ...)

The Empty Namespace

The empty namespace is the require namespace which points at the currently compiled module, and is denoted by the single colon :. It is added to add regularity, and avoid corner cases when walking recursive namespaces.

This means that the second program is equivalent to the one below:

(require io "lib/io.lisp")
(declare (hello-world (ascii "Hello, World!\n"))
         (main (function byte)))

(define main ()
 (declare)
 (io:println :hello-world))

Disallowing Namespaces In Declarations

spc adheres to the concept of namespace freezing, which means that a namespace cannot have names added to or removed from it, except in the module that the namespace is based off of.

The consequence of this is that functions, values, types, etc. cannot be defined within an already existing namespace - the following is forbidden:

(require a "a.lisp")
(declare (a:injected integer) ; Cannot modify existing namespace
         (main (function byte)))

(define main () ...)

The only namespace allowed within a declare or define block is either no namespace, or the default namespace:

(declare (main (function byte)))

(define :main () ...)

Implementation Concerns

  • Each module should get a unique prefix which is attached to all its names (exports and non-exports included), which has three properties ideally:

    1. It is unique across all modules in a program, so that name clashes do not occur.
    2. It is stable across all builds, so that full recompiles are not necessary to recalculate module prefixes.
    3. It does not require a modification of the program

    Alternatives include:

    • A hash of the filename (satisfies 1+3, does not satisfy 2)
    • Add a UUID of the filename, wherever a module exports anything (satisfies 1+2, does not satisfy 3)
  • Modules which export type aliases, but not the base types, should make sense. For example:

    ; a.lisp
    (require b "b.lisp")
    (declare (main (function byte)))
    
    (define main ()
     (declare
      (a somehow-defined))
     (block
      (set a (make-the-thing))
      ...))
    
    ; b.lisp
    (declare
     (thing (struct ...))
     (somehow-defined (alias thing))
     (make-the-thing (function somehow-defined)))
    (export *somehow-defined)
    
    (define make-the-thing ()
     (declare)
     (block
      ...
      (return ...)))