Factor's current module system and development model is heavily monolithic, reflecting Factor's history as a small-scale hobby project. The default settings encourage development within the Factor source tree and make it difficult to accommodate packaged read-only installations or projects developed outside of the source tree. Altering the module search path to accommodate these use cases is currently a manual and error-prone process. This proposal describes a new model for module lookup and installation with the following improvements:
- a standardized search path for modules, allowing for global, user-local, project-local, and package-local module installation and lookup
- a naming scheme for global and local modules
- a "package" concept to enable compartmentalization of development work and streamline distribution and use of Factor code outside of the main repository
- allow Factor to be installed following native platform conventions (
/usr/local
,.app
bundle/App Store,Program Files
) without modification or configuration - provide a standardized module search system that accommodates compartmentalized project development and does not require manual manipulation of search paths
- localize the package and module namespace to mitigate potential name collisions at the word, module, or package levels and further compartmentalize project development
- enable development of tools to automate installation and management of third-party source code
- streamline some inconvenient aspects of Factor module layout, collating metadata into a single file and accommodating quick-and-dirty development by not requiring directory hierarchies for every module
A module is a loadable unit of Factor source code. On disk, a module named foo
consists
of either:
- A standalone Factor source file
foo.factor
, or - A directory containing a source file
foo/foo.factor
and none, any, or all of the following optional metadata files:foo/foo-docs.factor
, containing documentation for the modulefoo/foo-tests.factor
, containing the test suite for the modulefoo/module.factormodule
, a YAML text file containing a top-level map form with none, any, or all of the following key-value pairs:author
, the author's name as a string, or a sequence of authors' names as stringsplatforms
, the platforms under which the module is supported as a sequence of strings. The module will refuse to load under platforms it is not supported.resources
, a sequence of strings of relative pathnames of files that are required by the modulesummary
, a string describing the moduletags
, a sequence of strings categorizing the module
Symbols defined in a source file are implicitly defined inside that source file's module. The
IN:
form to set the current module is only valid in interactive contexts. (A private form
IN-UNSAFE:
may be necessary for some low-level bootstrap code that currently uses IN:
in
nonstandard ways.)
The module name packages
is reserved, since this directory name is used as the package-local
package directory.
Factor source code is executed within the current module, the module that corresponds to that source file. The current module controls the following aspects of the Factor system:
- Definition forms create new words inside the current module.
- The current package, which controls module and package lookup, is the package in which the current module resides.
In an interactive context, the current module defaults to a virtual module (that is, a module
without backing source code) named scratchpad
in the current project. The current module may be
changed with the IN: module.name
form.
A package is a collection of related modules under a common subdirectory.
Any directory containing Factor source files may serve as a package directory.
A package comprises zero or more modules, stored inside the package directory hierarchically.
The module named foo.bar
is stored as a source file foo/bar.factor
or directory foo/bar
, as
described under Modules, inside the package directory. It is an error if both the
file foo/bar.factor
and directory foo/bar
exist in the same package.
The package directory may contain a packages
subdirectory; if present, this directory will
be searched for packages used locally by the package.
A package directory may contain a file package.factorpackage
, a YAML text file containing
a top-level map form with none, any, or all of the following key-value pairs:
-
name
, the canonical name of the package as a string. This need not correspond to the directory name or local installed name used to reference the package. -
uri
, a URI string identifying the canonical origin resource for this package, such as a version control repository or tarball. -
version
, a string identifying the version of the package. -
author
, the author's name as a string, or a sequence of authors' names as strings -
platforms
, the platforms under which the package is supported as a sequence of strings. Modules within the package will refuse to load on unsupported platforms. -
resources
, a sequence of strings of relative pathnames of files that are required by the package -
summary
, a string describing the package -
tags
, a sequence of strings categorizing the package -
requires
, a sequence of maps describing packages the package depends on. Each map must have the following key-value pairs:name
, the install name for the package as a string.uri
, a URI string identifying a resource from which the package can be installed, such as a version control repository or an archive file.version
, a string identifying the version of the package that should be installed. Ifuri
identifies a version control repository, this string may be used as a version or tag identifier.
The
name
anduri
specified for a needed package need not correspond to the package's own canonicalname
anduri
. -
requires-factor
, a string identifying the Factor version required by this package.
A package directory may contain a file image.factorimage
; if present, launching the Factor VM
from inside the package directory will load this image by default.
When loading source code, the current package is the package in which the current module resides. The current package controls the following aspects of the Factor system:
- Local module names are looked up inside the current package's directory.
- The current package's
packages
directory is searched first for packages referenced by absolute module names.
In an interactive context, the project is the active package being manipulated
by that session. When Factor is launched from the command line, the project defaults to the
current directory, and can be changed by the PROJECT:
form. From inside the Factor UI, projects
can be opened using the 'Open' menu item or by the PROJECT:
form from an open listener window.
Modules may be referenced in USING:
or similar forms by global name, absolute name, or local name.
Global names have the form of a URI, in other words, scheme://host/path
. The module source will
be looked for at that URI. file:
, git:
, git+ssh:
, http:
, and https:
URIs are supported.
Absolute names
have the form package:foo.bar
, which refers to the module at the path
foo/bar
or foo/bar.factor
in the package package
. An absolute name may appear with an empty
package name, such as :foo.bar
, which refers to a module in the standard factor
package.
Local names have the form foo.bar
,
which refers to the module at the path foo/bar
in the current package. Packages must work
independent of their installed name; it is an error for a package to refer to itself by absolute
name.
Local module names are looked up only in the current package directory. Absolute module names are looked up in the directory of the first package found by package lookup. Global module names (that is, URIs) are only searched for at the specified URI.
Modules referenced by a global name are considered to be standalone. There is no current package while loading a global named module, so such modules may not import using local module names and may only import by absolute or global module name.
When a package name is referenced, it is searched for in the following places in order:
- The package-local packages directory. This directory contains Factor packages installed for
use only by the current package. This directory is the
packages
subdirectory of the current package. - The user-local packages directory. This directory contains Factor packages installed by the
current user for all projects.
- Unix-style:
~/.factor/<version>/packages
- Apple-style:
~/Library/org.factorcode.Factor/<version>/packages
- Windows-style:
%USERPROFILE%\AppData\Factor\<version>\packages
- Unix-style:
- The site-local packages directory. This directory contains Factor packages installed
for all users on the current machine.
- Unix-style:
/usr/local/lib/factor/<version>/packages
- Apple-style:
/Library/org.factorcode.Factor/<version>/packages
- Windows-style:
%FACTOR_INSTALL_PATH%\packages
- Unix-style:
- The core directory. This directory contains the packages that make up Factor's standard
library and should not be altered outside of Factor's installation process.
- Unix-style:
/usr/local/lib/factor/<version>/core
- Apple-style:
Factor.app/Contents/Resources/core
- Windows-style:
%FACTOR_INSTALL_PATH%\core
- Unix-style:
If package lookup resolves a package name to the current package, it is an error. Packages should work independent of their installed name. Packages may not be interdependent.
Different packages may have different sets of packages installed in their package-local packages
directory. These package-local packages are only visible to modules in that package. The same
package name may refer to a different package-local package from the perspective of a different
package.
Factor comes with a standard package named factor
, whose name is reserved. It is an error if
the package name factor
resolves to anything other than the core factor
package distributed
with Factor.
Factor source code may use the following syntax forms to access symbols in other modules. These
syntax words are defined in the factor:syntax
module.
USING: module module ... ;
imports all public words from each module listed.USE: module
imports all public words frommodule
.FROM: module => word word ;
imports the named public words frommodule
.QUALIFIED: module ;
imports all public words frommodule
asmodule:name
.QUALIFIED-AS: module => alias ;
imports all public words frommodule
asalias:name
.
Imported symbols are private to the module; if a module foo
imports blub
from module bar
,
importers of foo
will not see blub
. The form EXPORT: word
will reexport word
through
the active module. The form EXPORTS: word word ... ;
will reexport all the listed words.
Factor's developer environments, such as the command-line listener and the UI, operate on an active
package, referred to as the project. Definition and import forms entered at a listener prompt
are resolved with the project as the current package.
The IN: module
form may be used from a listener prompt to change the active module. If IN:
names an absolute module path in another package, the active package for the listener is not changed.
A different project may be opened with PROJECT: path
, where path
is the filesystem path to
the new project directory.
The UI provides a document-based interface to projects. The 'Open' command in the UI opens a listener window with the chosen directory as its project.
- Add package-based lookup to the vocab loader.
- The vocab system may need structural changes to support packages.
- All import forms in all source code will need to be altered to use packaged module names.
- The modules currently shipped under
core
andbasis
can be packaged into afactor
package. The contents ofextra
, and perhaps some parts ofbasis
, can be broken up into separate packages and distributed separately. We could perhaps start by shipping a monolithicfactor-extra
package to put off more detailed breakup work. - Module metadata would be coalesced from the current multiple-text-files format into single YAML metadata files as described above.
- The build system should gain the ability to build and test not only the Factor distribution, but separately-distributed packages.
- Some new developer tools for maintaining and installing packages:
- A package installer for searching for and installing packages from a central repository, similar to gem or npm
- A bundle installer for installing package dependencies derived from package
requires
metadata - A metadata generator for packages, automatically serializing metadata from VCS and import forms used in the package's source code
- The UI should be given document-based features as described above. UI tool windows would be
associated with an active project, and the menu would provide
New
/Open
/Save
-type commands for creating, opening, and saving Factor projects
- Should modules still be called vocabularies for tradition's sake?
- There is no yaml parser in Factor currently, but there is a JSON parser. YAML is more convenient to hand-edit, but writing a parser is nontrivial. Since JSON is a subset of YAML, we could use JSON for module and package metadata files to begin with and generalize to YAML when/if a YAML parser is written.
- The
core
/basis
separation for bootstrapping is useful as a sandbox to prevent the bootstrap process from pulling in more than it's intended to. How to migrate the separation to the package system without exposing two separate standard modules? - The
package:module
naming scheme proposed requires all source code to be modified, which sucks. Should we use a naming scheme that allows some backward compatibility? - If we're going to change the module syntax anyway, should we also redesign import forms? A
single Java- or Python-style import syntax would be more elegant than
USING:
/QUALIFIED:
/FROM:
/etc. - Package dependencies may require more detailed version information, given Factor's infrequent release schedule and frequent compatibility breakage
- Would the
resource:
pseudo-path still be useful with this scheme? - Should the deploy tool work at the package level instead of/in addition to the module level?
It would be interesting to incorporate some of the ideas from
go
where you can directly install from VCS sources.Also, do you think it worth having a "project-level" where you can hardcode dependencies on a per-project basis?