aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/MODULES.md46
1 files changed, 46 insertions, 0 deletions
diff --git a/docs/MODULES.md b/docs/MODULES.md
new file mode 100644
index 0000000..2aa0333
--- /dev/null
+++ b/docs/MODULES.md
@@ -0,0 +1,46 @@
+# Modules and Namespacing
+
+Puck has a rich module system, inspired by such expressive systems in the ML family of languages, most notably including Rust. Unlike these systems, however, opening modules i.e. unqualified imports is *encouraged* - even in the global scope - which at first would appear to run contrary to the point of a module system. Puck cleans up such "namespace pollution" by **type-based disambiguation**. <!-- Puck allows for the usage of such unqualified identifiers by **type-based disambiguation**. -->
+
+```puck
+import std/[ascii, unicode]
+
+todo
+```
+
+Type-based disambiguation allows for the simultaneous seamless existence of functions with the same name and different type signature: and further, the usage of multiple functions with identical signatures and multiple types with identical names through explicit disambiguation. This is nothing fancy and follows some simple scoping rules:
+- A module cannot define two constants with the same name.
+- A module cannot define two functions with the same type signature.
+- A module cannot define two types with the same name and generic parameter count.
+- A module cannot define a constant and a zero-parameter function with the same name.
+- A module cannot define an enum and a function with the same name. (note: to be weakened)
+
+These unqualified imports by default may seem like madness to a Python or C programmer: but because Puck is *statically typed*, there is no ambiguity, and because of *uniform function call syntax*, it is usually clear what overloaded function is being called. Puck is also written with the modern tooling approach of language servers in mind: and so, navigating to the function declaration, in any IDE, should be two clicks or a keystroke away.
+
+```puck
+```
+
+Multiple modules can be imported in the same scope, of course, and so conflicts may arise on imports. In the pursuit of *some* explicitness, no attempt is made to guess the proper identifier from usage. These must be disambiguated by prefixing the module name, followed by a dot. This disambiguation breaks uniform function call syntax on functions: yet because functions only conflict when both their name and entire function signature overlap, this is a rare occurrence. If so desired, an import followed by `/[]` will force full qualification of all identifiers in the module - yet this is an antipattern and not recommended.
+
+Unrelated to the module system - but note that functions only differing in return type are allowed, albeit discouraged. Extra type annotations may be needed for the compiler to properly infer in such cases.
+
+```puck
+```
+
+This document has talked quite a lot about modules so far - with no mention so far of what modules actually *are*.
+
+At their basic level: modules are a scope, defined by the `module` keyword followed by a label, a colon, and the module body. As such, modules may contain other modules, and further identifiers within a nested module are not imported by importing the outer module by default. The `module` keyword itself, however, will often go fairly unused: because an implicit form of modules are **files** in the **filesystem structure**.
+
+The file structure forms an implicit, internal API, addressable by import statements using `..` and the previously-seen `module/submodule` syntax. Paths may be relative with `..` or absolute from the project root with `/`. Moving and renaming files will break imports, of course, so some care must be taken while refactoring. This will hopefully be a non-issue with the aid of lightweight tooling.
+
+```puck
+```
+
+When linked as a library, however, the file structure is not visible - and so one must define an "external API", in the main file of the library. This is aided by the `export` statement which can be used to re-export imported modules as submodules of the existing module. Such an external API allows for significant flexibility in defining the structure of a library. Exports may be of a full module (`export module`), effectively merging it with the current module, or of individual items within the external module `export module/[one, two, three]`. Such exports may be placed within a new `module` scope for even more flexibility.
+
+Modules do not export everything in their scope: indeed, all identifiers within a module are **private by default**, and must be explicitly marked public by use of the `pub` keyword. In support of encapsulation, fields of types, too, are considered separate from the type itself and also must be `pub` to be accessible. Identifiers from imported modules, of course, are not considered part of the current module unless explicitly exported.
+
+```puck
+```
+
+todo: explore the relation between module systems and typeclasses. are hot-swappable types in modules (i.e. self Self) worth it, or would such a thing be better accomplished by `int`, `string`, etc being typeclasses?