Chapter 10: Loading Code

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 git).

Require

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:

1require "some_file.mt"
2base = "folder/path/"
3require base + "file.mt"

Any expression that does not evaluate to a String will raise an error.

Absolute paths

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.

1require "/home/me/src/foo.mt"
2require "/etc/scripts/bar.mt"

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

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.

1require "./foo/bar.mt"
2require "../../lib/util.mt"

Resource paths

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 require expression.

Most often, resource paths are used to load third-party code or stdlib components:

1require "http.mt"
2require "foo.mt"

Reloading files

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 false.

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:

1require "./foo.mt"
2require "./bar/baz/../../foo.mt"

File Structure

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.

Single responsibility

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:

1# In the file "foo.mt"
2defmodule Foo
3end
4
5# In another file, "bar.mt"
6defmodule Bar
7end

Module folders

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:

 1# Structure:
 2#
 3# root
 4# |- foo.mt
 5# \- foo
 6#    |- bar.mt
 7#    \- baz.mt
 8
 9# In foo.mt
10defmodule Foo
11  def create
12    # ...
13  end
14end
15
16# In foo/bar.mt:
17
18defmodule Foo
19  deftype Bar
20  end
21end
22
23# In foo/baz.mt
24
25defmodule Foo
26  defmodule Baz
27  end
28end

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:

1# In foo.mt
2defmodule Foo
3end
4
5require "./foo/bar.mt"
6require "./foo/baz.mt"

With this pattern, end-users can simply require the foo.mt file and get all of the submodules included automatically:

1require "./foo.mt"
2
3%Foo.Bar{} #=> new Bar instance

Get Started

Introduction Values and Variables Basic Operations Flow Control Pattern Matching Functions and Clauses Modules Types and Self Blocks and Anonymous Functions Exception Handling Loading Code