So far, all of the examples we've seen have had their source code live in a single file. But, as a codebase grows, keeping everything in a single file will inevitably get messy.
Splitting code into multiple files makes helps with readability, usability, and traceability (through version control systems such as
Myst provides a single way to include code from another file: the
require keyword. Using
require will instruct the program to search the file system for a file with the name given by its argument.
The argument to a require can be any expression that evaluates to a String:
Any expression that does not evaluate to a String will raise an error.
The most strict path specification for a file is an absolute path. These paths start at the root directory of the file system, following the given path to find the target file.
Any path starting with a forward slash (
/) is considered absolute. In almost all cases, absolute paths should be avoided with preference to relative or resource paths, as the code will inherently be less portable. Only use absolute paths for scripts with static locations on the system (basically never).
Relative paths are the most common paths for requiring code in userland. Relative paths start either with a single (
.) or double (
..) dot, just like in plain Unix.
Relative paths will be looked up relative to the file that the
require is given in, not necessarily the root directory of the project.
The last type of path that can be given is referred to as a resource path. Resource paths check a number of paths for the existence of a file, starting with the directories given by the
MYST_PATH shell environment variable.
If this variable is not set, or if no matching file is found in any of those paths, the current working directory (given by the
pwd shell command) will be checked. Finally, the path will be treated as a relative path from the file containing the
Most often, resource paths are used to load third-party code or stdlib components:
Myst does not currently provide a mechanism for reloading a file. Once a file has been required in a program, it will be remembered, and any future attempts to require it will simply return
Files that have already been required are tracked by their absolute path, not necessarily the path that was given in the expression. For example, both of these paths refer to the same file, so only the first
require will actually execute:
While there is no enforced file structure for a Myst-based project, there are a few general guidelines that will help keep your codebase easy to navigate and work with.
Much like the single responsibility principle for modules and classes, each source file should generally have a single responsibility.
Most often, this lines up well with having a single module or type per file, though some deeply connected objects may make more sense in a single file, and a single type may be better spread into multiple files.
Giving each file a single responsibility also helps with naming conventions. In most cases, the file name can just match the name of the type or module it contains. For example:
When a module contains multiple types or modules within it, each should still be given their own file, but those files should be kept in a folder with the name of the containing module.
If there is any top-level code for the module, a file with the same name can also be given:
Another way of using the top-level file for a module is to require all of the submodules and types, to simplify the end-user's experience when requiring the module:
With this pattern, end-users can simply require the
foo.mt file and get all of the submodules included automatically: