Janet 1.38.0-73334f3 Documentation
(Other Versions:
1.37.1
1.36.0
1.35.0
1.34.0
1.31.0
1.29.1
1.28.0
1.27.0
1.26.0
1.25.1
1.24.0
1.23.0
1.22.0
1.21.0
1.20.0
1.19.0
1.18.1
1.17.1
1.16.1
1.15.0
1.13.1
1.12.2
1.11.1
1.10.1
1.9.1
1.8.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
)
Modules
As programs grow, they should be broken into smaller pieces for maintainability. Janet has the concept of modules to this end. A module is a collection of bindings and its associated environment. By default, modules correspond one-to-one with source files in Janet, although you may override this and structure modules however you like.
Installing a module
Using jpm
, the path
module can be installed like so from
the command line:
sudo jpm install https://github.com/janet-lang/path.git
The use of sudo
is not required in some setups, but is often
needed for a global install on POSIX systems. You can pass in a git
repository URL to the install
command, and jpm
will
install that package globally on your system.
If you are not using jpm
, you can place the file
path.janet
from the repository in your current directory, and
Janet will be able to import it as well. However, for more complicated
packages or packages containing native C extensions, jpm
will
usually be much easier.
Importing a module
To use a module, the best way is to use the (import)
macro,
which looks for a module with the given name on your system and
imports its symbols into the current environment, usually prefixed
with the name of the module.
(import path)
(path/join (os/cwd) "temp")
Once path
is imported, all of its symbols are available to the
host program, but prefixed with path/
. To import the symbols
with a custom prefix or without any prefix, use the :prefix
argument to the import
macro.
(import path :prefix "")
(join (os/cwd) "temp")
You may also use the :as
argument to specify the prefix in a
more natural way.
(import path :as p)
(p/join (os/cwd) "temp")
By default, the following files will be searched for in order for
(import foo)
:
* foo.jimage
* foo.janet
* foo/init.janet
* foo.<native-extension>
where <native-extension>
is the file extension for shared
objects / libraries for the current platform (e.g. `dll` for Windows,
`dylib` for macos, and `so` for other unix-likes). This is a
"first-match wins" arrangement so at most one file will be imported.
Custom loaders (module/paths
and module/loaders
)
The module/paths
and module/loaders
data structures
determine how Janet will load a given module name. module/paths
is a list of patterns and methods to use to try and find a given
module. The entries in module/paths
will be checked in order,
as it is possible that multiple entries could match. If a module name
matches a pattern (which usually means that some file exists), then we
use the corresponding loader in module/loaders
to evaluate that
file, source code, URL, or whatever else we want to derive from the
module name. The resulting value, usually an environment table, is
then cached so that it can be reused later without evaluating a module
twice.
By modifying module/paths
and module/loaders
, you can
create custom module schemes, handlers for file extensions, or even
your own module system. It is recommended to not modify existing
entries in module/paths
or module/loader
, and simply add
to the existing entries. This is rather advanced usage, but can be
incredibly useful in creating DSLs that feel native to Janet.
module/paths
This is an array of file paths to search for modules in the file
system. Each element in this array is a tuple [path type
predicate]
, where path
is a templated file path, which
determines what path corresponds to a module name, and where
type
is the loading method used to load the module. type
can be one of :native
, :source
, :image
, or any
key in the module/loaders
table.
predicate
is an optional function or file extension used to
further filter whether or not a module name should match. It's
mainly useful when path
is a string and you want to further
refine the pattern.
path
can also be a function that checks if a module name
matches. If the module name matches, the function should return a
string that will be used as the main identifier for the
module. Most of the time, this should be the absolute path to the
module, but it can be any unique key that identifies the module
such as an absolute URL. It is this key that is used to determine
if a module has been loaded already. This mechanism lets
./mymod
and mymod
refer to the same module even
though they are different names passed to import
.
module/loaders
Once a primary module identifier and module type has been chosen,
Janet's import machinery (defined mostly in require
and
module/find
) will use the appropriate loader from
module/loaders
to get an environment table. Each loader in the
table is just a function that takes the primary module identifier
(usually an absolute path to the module) as well as optionally any
other arguments passed to require
or import
, and returns
the environment table. For example, the :source
type is a thin
wrapper around dofile
, the :image
type is a wrapper
around load-image
, and the :native
type is a wrapper
around native
.
URL loader example
An example from examples/urlloader.janet
in the source
repository shows how a loader can be a wrapper around curl and
dofile
to load source files from HTTP URLs. To use it, simply
evaluate this file somewhere in your program before you require code
from a URL. Don't use this as-is in production code, because if
url
contains spaces in load-url
, the module will not
load correctly. A more robust solution would quote the URL.
(defn- load-url
[url args]
(def p (os/spawn ["curl" url "-s"] :p {:out :pipe}))
(def res (dofile (p :out) :source url ;args))
(:wait p)
res)
(defn- check-http-url
[path]
(if (or (string/has-prefix? "http://" path)
(string/has-prefix? "https://" path))
path))
# Add the module loader and path tuple to right places
(array/push module/paths [check-http-url :janet-http])
(put module/loaders :janet-http load-url)
Relative imports
You can include files relative to the current file by prefixing the
module name with ./
. For example, suppose you have a file tree
that is structured like so:
mymod/
init.janet
deps/
dep1.janet
dep2.janet
With the above structure, init.janet
can import dep1
and
dep2
with relative imports. This is robust because the entire
mymod/
directory can be installed without any chance that
dep1
and dep2
will overwrite other files, or that
init.janet
will accidentally import a different file named
dep1.janet
or dep2.janet
. mymod
can even be a
sub-directory in another Janet source tree and work as expected.
init.janet
(import ./deps/dep1 :as dep1)
(import ./deps/dep2 :as dep2)
...
Note that relative imports are relative to the current file, not to the working directory. For that behavior, see the section on working directory imports.
Pre-loaded Modules
An easy way to bypass the import system and create custom modules is
to manually add modules into module/cache
.
(put module/cache "mymod" (dofile "localfile.janet"))
# The module defined by localfile.janet can now be
# imported with (import mymod)
This is not recommended except in a few circumstances where a module
needs to be loaded at runtime but doesn't exist on disk, such as if a
program were compiled into a standalone executable. A pattern to allow
this in a standalone executable built with jpm
is as follows:
(def mod1 (require "./localfile"))
(defn main [& args]
(put module/cache "mod1" mod1)
# now calls to (import mod1) at runtime will succeed.
(import mod1))
Working Directory Imports
Starting in 1.14.1, Janet will allow imports relative to the current
working directory. This is very useful in scripting circumstances
where source code must be loaded at runtime. This can be done with
the function dofile
, but it can also be done with the default
import system.
To import a file relative to the working directory, prefix the import path with a forward slash "/".
(import /local)
This will import from a file ./local.janet
, (or
./local.so
, ./local.jimage
, and so on), and prefix the
imported symbols with local/
.
@-prefixed Imports
Starting in 1.26.0, Janet allows importing modules from custom
directories more easily using the @
-prefix in a module path.
For example, if there is a dynamic binding :custom-modules
that
is a file system path to a directory of modules, import from that
directory with:
(import @custom-modules/mymod)
As a special case, it is possible to import from absolute paths by
prefixing an absolute path with @
. For example, to import
from /tmp/custom-modules/mymod.janet
, express this as:
(import @/tmp/custom-modules/mymod)
Note that although using absolute paths is possible and useful for testing, it is not recommended for most production use cases.
Writing a module
Writing a module in Janet is mostly about exposing only the public
functions that you want users of your module to be able to use. All
top level functions defined via defn
, macros defined via
defmacro
, constants defined via def
, and vars defined
via var
will be exported in your module. To mark a function or
binding as private to your module, you may use defn-
or
def-
at the top level. You can also add the keyword
:private
as metadata for the binding.
Sample module:
# Put imports and other requisite code up here
(def api-constant
"Some constant."
1000)
(def- private-constant
"Not exported."
:abc)
(var *api-var*
"Some API var. Be careful with these, dynamic bindings may be better."
nil)
(var *private-var* :private
"var that is not exported."
123)
(defn- private-fun
"Sum three numbers."
[x y z]
(+ x y z))
(defn api-fun
"Do a thing."
[stuff]
(+ 10 (private-fun stuff 1 2)))
To import our sample module given that it is stored on disk at
mymod.janet
, we could do something like the following (this
also works in a REPL):
(import mymod)
mymod/api-constant # evaluates to 1000
(mymod/api-fun 10) # evaluates to 23
mymod/*api-var* # evaluates to nil
(set mymod/*api-var* 10)
mymod/*api-var* # evaluates to 10
(mymod/private-fun 10) # will not compile