Modules
- Public/Private Members
- Submodules
- Parents, Children, Siblings
- Module Caching
- Circular Imports
- Aliasing
- Importing Members
- Import Roots
- Executing Module Directories
- Importing using $exec()
- Importing using $import()
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.
Note that only the final module name in the import chain is declared as a variable in the importing scope, e.g.
import foo::bar::baz;
In this case, only baz is declared as a variable in the importing scope.
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.
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 using $exec()
You can import a file as a module using the $exec() function, which executes a string of Pyro source code as a new module, e.g.
var code = $read_file("path/to/file.pyro"); var mod = $exec(code);
You can also use the $exec() function to create a new module directly from a string, e.g.
var mod = $exec(`pub def func() { return "foobar"; }`); assert mod::func() == "foobar";
Importing using $import()
You can import a module using the $import() function which takes the module's import path as a string, e.g.
var mod = $import("std::math"); assert mod::abs(-1) == 1;
You can wrap the $import() function in a try expression to check if a module exists, e.g.
var mod = try $import("foo::bar::baz"); if $is_err(mod) { echo "not found"; }