aboutsummaryrefslogtreecommitdiff
path: root/docs/MODULES.md
blob: d387e0a8baf029afb782e662a4bf18829ce92080 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 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.

Modules and identifiers from modules may be imported and exported `as` a different name.

```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?