Modules
- Public/Private Members
- Submodules
- Parents, Children, Siblings
- Module Caching
- Circular Imports
- Aliasing
- Importing Members
- Import Roots
- Executing Module Directories
- Importing Files/Strings
A module is a Pyro file loaded as a library. Modules are loaded using the import keyword.
Assume we have a file called math.pyro containing math functions:
import math;
The imported module's name becomes a variable in the importing scope:
assert math::abs(-1) == 1;
Use the member access operator :: to access a module's top-level members — i.e. variables, functions, and classes.
Public/Private Members
A module's top-level members (variables, functions, and classes) are private by default
— use the pub keyword to make them public, e.g.
pub var number = 123; pub def func() { return "hello world!"; } pub class Object { var value; }
- Public members can be accessed from inside or outside the module.
- Private members can only be accessed from inside the module.
Submodules
Modules can contain submodules. Assume we have a directory called math containing a file called trig.pyro:
import math::trig; var value = trig::cos(1);
The top-level module math can live alongside the directory that contains its submodules:
root/
|-- math.pyro
|-- math/
|-- trig.pyro
Alternatively, it can live inside the math directory in a file called self.pyro:
root/
|-- math/
|-- self.pyro
|-- trig.pyro
Finally, the top-level math module can be empty and simply function as a container for its submodules:
root/
|-- math/
|-- trig.pyro
Submodules can contain submodules of their own — there's no hard limit to how deep the nesting can go.
Parents, Children, Siblings
Imagine we have the following module structure:
root/
|-- math/
|-- calc.pyro
|-- self.pyro
|-- trig.pyro
A parent module can import its child – e.g. in root/math/self.pyro:
import math::trig;
A child module can import its parent – e.g. in root/math/calc.pyro:
import math;
A child module can import its sibling – e.g. in root/math/calc.pyro:
import math::trig;
Module Caching
A module file is only executed the first time it's imported. The imported module is cached and any subsequent imports simply load the module object from the cache.
Importing a submodule automatically executes and adds its ancestor modules to the cache if they haven't already been imported. For example, given the statement:
import foo::bar::baz;
-
The module
foowill be executed and added to the cache if it hasn't already been imported. -
The module
foo::barwill be executed and added to the cache if it hasn't already been imported. -
The module
foo::bar::bazwill be executed and added to the cache if it hasn't already been imported.
Only the final module name in the chain is declared as a variable in the importing scope — here baz.
Circular Imports
In general, circular imports are fine — module foo can import module bar and module bar can import module foo.
Note that trying to use a member from an imported module that hasn't finished initializing can result in a panic.
Note that you can avoid the problems that sometimes result from top-level circular imports by importing the required module inside a function, e.g.
def do_some_math(value) { import math; return math::abs(value) * math::pi; }
This import will only fire when the function is run. (Imported modules are cached so the module won't be re-imported every time the function runs.)
Aliasing
You can import a module under an alias using an import ... as ... statement:
import math as alias; var foo = alias::abs(-1);
Submodules can similarly be aliased:
import math::trig as alias; var foo = alias::cos(1);
Importing Members
You can import a top-level member from a module by wrapping its name in braces, e.g.
import math::{abs}; assert abs(-1) == 1;
You can alias the imported member using an import ... as ... statement, e.g.
import math::{abs} as foo; assert foo(-1) == 1;
You can import multiple members in a single import statement by separating their names with commas, e.g.
import math::{cos, sin};
You can alias the imported members using a comma-separated as list, e.g.
import math::{cos, sin} as foo, bar;
You can import all the top-level members from a module using an asterisk, e.g.
import math::{*};
Note that you can only use this {*} syntax at global scope in a script or module file — i.e. you can't import {*} inside a block or function.
Import Roots
The global $roots vector contains the list of root directories that Pyro checks when attempting to import a module.
-
When Pyro is executing a script it adds the parent directory containing the script to the
$rootsvector, along with amodulesdirectory within that parent directory. (This means that the modules required by a script can be located either in the same directory as the script or in amodulesdirectory alongside the script.) -
When Pyro is executing the REPL it adds the current working directory to the
$rootsvector.
To customize Pyro's import behaviour:
-
You can edit the
$rootsvector within a Pyro program to add or remove entries. -
You can use the
--import-rootcommand-line option to add additional root directories. -
You can use a
PYRO_IMPORT_ROOTSenvironment variable to specify additional root directories. The value should be a list of:separated directory paths.
Executing Module Directories
You can execute a module directory as a script if it contains a self.pyro file:
$ pyro path/to/module/directory
Pyro executes the self.pyro file, then the $main() function if it finds one.
Importing Files/Strings
You can import any file as a module using the $exec() function, which executes a string of Pyro source code as a new module, e.g.
var source_code = $read_file("path/to/file.pyro"); var module = $exec(source_code);
Unlike the import statement, this doesn't add the new module to the cache.
You can also use the $exec() function to create a new module directly from a string, e.g.
var module = $exec(`pub def func() { return "foobar"; }`); assert module::func() == "foobar";