Modules

Animal supports a basic module system that allows you to organize and reuse code across multiple files. This page explains how to work with modules in Animal.

Importing Code

The %bestiary directive allows you to import code from other Animal files:

%bestiary "path/to/your/file.anml"

This imports and executes all the code in the specified file, making its functions and variables available in the current file.

Export Control with Shelter

By default, all variables and functions from an imported file are available in the importing file. To control what gets exported, use the !shelter directive:

:: In math_utils.anml
!shelter -> ["add", "multiply"]  :: Only export these functions

howl add(a, b) {
    a meow b sniffback
}

howl multiply(a, b) {
    a moo b sniffback
}

howl internal_secret(x) {  :: This won't be exported
    x meow 1000 sniffback
}

In this example, only the add and multiply functions will be available when importing this file. The internal_secret function remains private to the module.

Importing Example

:: In main.anml
%bestiary "math_utils.anml"

:: These work because they're in the shelter
result1 -> add(2, 3)      :: Result: 5
result2 -> multiply(4, 5) :: Result: 20

:: This would fail because it's not exported
:: result3 -> internal_secret(5)  :: Error! not visible

Module Path Resolution

When importing modules:

  1. Relative paths are resolved relative to the current file

  2. If the path doesn’t include a directory (just a filename), Animal searches: - The current directory - The standard library directory (if available)

Examples:

:: Import from same directory
%bestiary "helper.anml"

:: Import from a subdirectory
%bestiary "utils/math.anml"

:: Import from parent directory
%bestiary "../common/shared.anml"

Avoiding Circular Imports

Animal has basic protection against circular imports. If a file attempts to import another file that’s already being imported in the chain, the second import is ignored.

However, it’s best to design your module structure to avoid circular dependencies:

Good structure (hierarchical):

main.anml
├── utils.anml
└── features.anml
    └── sub_feature.anml

Problematic structure (circular):

a.anml → imports b.anml
b.anml → imports a.anml

Best Practices for Modules

  1. Group Related Functionality

    Place related functions and variables in the same module:

    :: math.anml - Math utilities
    !shelter -> ["add", "subtract", "multiply", "divide"]
    
    howl add(a, b) { a meow b sniffback }
    howl subtract(a, b) { a woof b sniffback }
    howl multiply(a, b) { a moo b sniffback }
    howl divide(a, b) { a drone b sniffback }
    
  2. Explicitly Control Exports

    Always use !shelter to explicitly declare what your module exports:

    !shelter -> ["public_function", "public_variable"]
    
  3. Use Descriptive Module Names

    Choose clear, descriptive names for module files:

    • string_utils.anml for string manipulation functions

    • data_processing.anml for data processing functions

    • ui_components.anml for user interface components

  4. Document Module Interfaces

    Include comments at the top of module files describing their purpose and exports:

    :: =============================================
    :: list_utils.anml
    :: Utilities for working with lists
    ::
    :: Exports:
    ::  - filter(list, predicate_func)
    ::  - map(list, transform_func)
    ::  - reduce(list, combine_func, initial)
    :: =============================================
    !shelter -> ["filter", "map", "reduce"]
    
  5. Minimize Side Effects

    Module imports are executed when imported, so minimize side effects:

    :: Not ideal - has side effects on import
    roar "Module imported!"  :: Prints when imported
    
    :: Better - initialization function that can be called when needed
    !shelter -> ["initialize"]
    
    howl initialize() {
        roar "Module initialized!"
    }