aboutsummaryrefslogtreecommitdiff
path: root/docs/MODULES.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MODULES.md')
-rw-r--r--docs/MODULES.md43
1 files changed, 15 insertions, 28 deletions
diff --git a/docs/MODULES.md b/docs/MODULES.md
index 4f5bb70..bbce909 100644
--- a/docs/MODULES.md
+++ b/docs/MODULES.md
@@ -1,50 +1,37 @@
# Modules and Namespacing
-Puck has a rich module system, inspired by such expressive systems in the ML family of languages, notably including Rust and OCaml. 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. Such "namespace pollution" is made a non-issue by **type-based disambiguation** (and can be avoided regardless with qualified imports anyway).
+Puck has a first-class module system, inspired by such expressive designs in the ML family.
-A major goal of Puck's module system is to allow the same level of expressiveness as the ML family - while cutting down on the extraneous syntax and boilerplate needed to do so. As such, access modifiers are written directly inline with their declaration, and the file system structure is reused to form an implicit module system for internal use.
+## Using Modules
```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)
+Modules package up code for use by others. Identifiers known at compile time may be part of a *module signature*: these being constants, functions, macros, types, and other modules themselves. They may be made accessible to external users by prefixing them with the `pub` keyword. Files are modules, named with their filename. The `mod` keyword followed by an identifier and an indented block of code explicitly defines a module, inside of the current module. Modules are first class: they may be bound to constants (having the type `: mod`) and publicly exported, or bound to local variables and passed into functions for who knows what purpose.
-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.
+The `use` keyword lets you use other modules. The `use` keyword imports public symbols from the specified module into the current scope *unqualified*. This runs contrary to expectations coming from most other languages: from Python to Standard ML, the standard notion of an "import" usually puts the imported symbols behind another symbol to avoid "polluting the namespace". As Puck is strongly typed and allows overloading, however, the author sees no reason for namespace pollution to be of concern. These unqualified imports have the added benefit of making uniform function call syntax more widely accessible. It is inevitable that identifier conflicts will exist on occasion, of course: when this happens, the compiler will force qualification (this then does restrict uniform function call syntax).
```puck
```
-Multiple modules can be imported in the same scope, 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.
+Nonetheless, if qualification of imports is so desired, an alternative approach is available - binding a module to a constant. Both the standard library and external libraries are available behind identifiers without use of `use`: `std` and `lib`, respectively. (FFI and local modules will likely use more identifiers, but this is not hammered out yet.) A submodule - for example, `std.net` - may be bound in a constant as `const net = std.net`, providing all of the modules' public identifiers for use, as fields of the constant `net`. We will see this construction to be extraordinarily helpful in crafting high-level public APIs for libraries later on.
-```puck
-```
+Multiple modules can be imported at once, i.e. `use std.[logs, tests]`, `use lib.crypto, lib.http`. The standard namespaces (`std`, `lib`) deserve more than a passing mention. There are several of these: `std` for the standard library, `lib` for all external libraries, `crate` for the top-level namespace of a project (subject to change), `this` for the current containing module (subject to change)... In addition: there are a suite of *language* namespaces, for FFI - `rust`, `nim`, and `swift` preliminarily - that give access to libraries from other languages. Recall that imports are unqualified - so `use std` will allow use of the standard library without the `std` qualifier (not recommended: several modules have common names), and `use lib` will dump every library it can find into the global namespace (even less recommended).
-This document has talked quite a lot about modules so far - with no mention so far of what modules actually *are*.
+## Implicit Modules
-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**.
+A major goal of Puck's module system is to allow the same level of expressiveness as the ML family, while cutting down on the extraneous syntax and boilerplate needed to do so. As such, access modifiers are written directly inline with their declaration, and the file system structure is reused to form an implicit module system for internal use. This - particularly the former - *limits* the structure a module can expose at first glance, but we will see later that interfaces recoup much of this lost specificity.
-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.
+We mentioned that the filesystem forms an implicit module structure. This begets a couple of design choices. Module names **must** be lowercase, for compatibility with case-insensitive filesystems. Both a file and a folder with the same name can exist. Files within the aforementioned folder are treated as submodules of the aforementioned file. This again restricts the sorts of module structures we can build, but we will again see later that this restriction can be bypassed.
-```puck
-```
+The `this` and `crate` modules are useful for this implicit structure...
-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.
+## Defining Interfaces
-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. Some languages allow for individual fields of structs to be marked public, in better support of encapsulation. Unfortunately, this fundamentally breaks structural typing for a variety of reasons, and so is not supported. Only a binary distinction between fully visible and fully opaque types is allowed. Identifiers from imported modules are not considered part of the current module unless explicitly exported, of course.
+...
-Modules and identifiers from modules may be imported and exported `as` a different name.
+## Defining an External API
-```puck
-```
+The filesystem provides an implicit module structure, but it may not be the one you want to expose to users.
-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?
+...