Packages in Gypsum and CodeSwitch
I recently merged an important new feature into Gypsum and CodeSwitch: packages!
If you haven't been following, Gypsum is a new, experimental programming language I'm working on, and CodeSwitch is the virtual machine that runs it. You might want to read this introduction. Gypsum and CodeSwitch are open source and can be found on GitHub.
Anyway, back to packages. Many languages have something similar to packages: Python has modules, C++ has libraries, Java has... packages. Packages are named bundles of related code. They make code easier to understand by creating an abstraction boundary. Package authors can define an interface, consisting of public classes and functions. They can implement that interface using private definitions, which are not visible to users. Package users only need to be concerned about the public interface; the private implementation can be changed without breaking anyone.
Packages also make code easier to build and distribute. Each package is stored in a single file, which can be distributed on its own. Each package has a unique name, a version, and a list of other packages it depends on (with optional minimum and maximum versions for each). When you load a package in CodeSwitch, the VM will automatically load, link, and initialize any dependencies recursively.
A simple example
Suppose we have the set of classes below, implementing a linked list. I showed something similar in a previous article.
// list.gy // Public base class for lists in general. public abstract class List[+T] public abstract def length: i64 public abstract def head: T public abstract def tail: List[T] // Non-public singleton class for the empty list. class Nil-class <: List[Nothing] public def length = 0 public def head = throw Exception public def tail = throw Exception // The empty list (the only public instance of `Nil-class`). // Note that this must not have the type `Nil-class`, since that // class is not visible to users. We use `List[Nothing]` instead. public let Nil: List[Nothing] = Nil-class // Public class for non-empty lists. public class Cons[+T](value: T, next: List[T]) <: List[T] public def length = 1 + next.length public def head = value public def tail = next
We can compile this example into a package.
$ compiler list.gy --package-name list --package-version 1 \ -o packages/list-1.csp
Let's suppose we have another program which uses this package.
// test.gy def main = let list = list.Cons[String]("foo", list.Cons[String]("bar", list.Cons[String]("baz", list.Nil))) print(list.length + "\n")
In this program, we can refer to definitions in the list
package by prefixing them with "list.
". This program can be compiled as below.
$ compiler --package-path packages test.gy -o test.csp
Note that we have to pass the packages
directory to the compiler using --package-path
. This argument can be passed multiple times. The compiler will search each of these directories for packages and load them in as they are referenced. The compiler will add some dependency metadata for each foreign definition that is referenced.
We can execute this program as below:
$ CS_PACKAGE_PATH=packages driver test.csp
We pass the package directories to the VM using the environment variable CS_PACKAGE_PATH
(this variable is also recognized by the compiler). When the VM loads test.csp
, it will load list-1.csp
as well, since that package is listed as a dependency in test.csp
.
Future work
Now that packages are in place, I've already started working on a standard library. You can find it in the std directory. Right now, I'm working on tuples and options.
The compiler automatically adds the standard library as a dependency to every package that gets compiled unless the --no-std
option is passed on the command line. This will disable language features that depend on the standard library (just tuples so far).
I'm planning to work on imports soon. Imports will allow you to reference definitions in other packages without writing out the whole package name every time. I'd like this to be a powerful mechanism such as what Scala has: I'd like to be able to import definitions from classes and objects, not just packages. It may take a while to get this working though.