Runtime

The Basilisp runtime is vast and comprised of many different moving parts. The primary components that users will interface with, however, are Namespaces and Vars.

Namespaces

Namespaces are the primary unit of code organization provided by Basilisp. To the Basilisp runtime, a Namespace is simply a mapping of names to objects (objects, strings, etc.); this mapping is known as a Var. In practice, Namespaces are typically written and organized as individual files in the filesystem. Namespaces may be nested to produce a deeper source tree where that makes sense to do.

Users create Namespaces using the basilisp.core/ns macro at the top of every code file. Namespace names should match the document’s place in the source tree, with forward slash (/) characters being replaced by periods (.) and underscores (_) replaces with hyphens (-).

(ns myproject.ns
  "This namespace does x, y, and z."
  (:require
   [basilisp.string :as str])
  (:import datetime))

Under the hood, this magic macro will do a bunch of convenient setup that users would otherwise have to do themselves. First, it will refer the public contents of basilisp.core, allowing unqualified references to all functions in that namespace anywhere within the new namespace. Secondly, it will require the namespace basilisp.string which makes the functions from that namespace available in the current namespace using the shortened prefix str. That means within the newly created namespace you will be able to refer to basilisp.string/alpha? as simply str/alpha?. Afterwards, the ns macro will import the Python module datetime, which we can use in a similar way, referring to objects as datetime/date for instance. Finally, ns will set the dynamic Var basilisp.core/*ns* to myproject.ns, which informs the compiler that any new Vars or functions defined right now should be associated with myproject.ns, not any other namespace.

Note

This documentation frequently conflates namespaces and physical files for simplicity’s sake. It is not necessary for a namespace to be defined in only one file, nor is it necessary that one file contain only one namespace. However, while these practices are technically permitted, they are generally discouraged since it makes it harder to figure out where code is defined and organized.

Requires

Requires are the primary way users establish linkages between different namespaces, similarly to how Python’s import statement connects different Python modules and packages. In typical usage, namespaces are required in the ns preamble at the top of every code file. In a REPL context, it may make sense to use the require function to require a namespace in an ad-hoc fashion during development. Both tools generally provide the same set of capabilities, as the ns form ultimately compiles down to calls to the function require.

Required namespaces may be required using their full name, aliased to a shorter name (such as str for basilisp.string), or even have individual Vars from the namespace referred in and reference as if they were defined in the same namespace. Users may even combine the options together. When using the :refer feature of require, users may also choose to instead refer all Vars from the target namespace by using the :all keyword in place of the vector of Var names to require.

(ns myproject.ns
  (:require
   [basilisp.string :as str]
   [basilisp.io]
   [basilisp.edn :as edn :refer [read-string]]
   [basilisp.set :refer :all]
   [basilisp.walk :refer [walk]]))

Warning

Referring :all Vars from another namespace is generally discouraged, since it can clog up the namespace with potentially unused names and can make it challenging for readers to figure out where a Var came from.

As noted above in Namespaces, the ns macro performs an implicit [basilisp.core :refer :all] by default, allowing users to refer to all core functions without qualification. In general this is desirable, since you will be interacting with basilisp.core a lot. However, in some cases, you may wish to suppress certain Vars from being referred, particularly if you are defining Vars with clashing names. In such cases, you can instruct the ns macro to exclude specific Vars from basilisp.core:

(ns myproject.ns
  (:refer-basilisp :exclude [get]))

There are other filtering and selection criteria which can be included on both :refer-basilisp and :require sections of the ns macro. See the documentation for require for more details.

Note

require and the (:require ...) form of ns are the preferred methods for requiring namespaces and referring Vars.

refer and use are both older, more limited functions which refer has subsumed and they are only included for Clojure compatibility.

Vars

Vars are mutable reference types which hold a reference to something. Users typically interact with Vars with the def form and the basilisp.core/defn macro which create Vars to hold he result of the expression or function. All values created with these forms are stored in Vars and interned in a Namespace so they can be looked up later. The Basilisp compiler uses Vars interned in Namespaces during name resolution to determine if a name is referring to a local name (perhaps in a let binding or as a function argument) or if it refers to a Var.

Vars may have metadata, which generally originates on the name symbol given during a def. Specific metadata keys given during the creation of a Var can enable specific features that may be useful for some Vars.

Dynamic Vars

Vars created with the ^:dynamic metadata key are known as “dynamic” Vars. Dynamic Vars include a thread-local stack of value bindings that can be overridden using the basilisp.core/binding macro. This may be a suitable alternative to requiring users to pass in an infrequently changing value as an argument to your function. Basilisp uses this in basilisp.core with things such as *in*, *out*, and *data-readers*.

For example, if you wanted to fetch all of the data being printed to *out* as a string, you could trivially do so with this construct:

(import io)
(let [s (io/StringIO)]
  (binding [*out* s]
    ...)
  (.getvalue s))

Note that this functionality already exists as with-out-str, but it serves as a good example of how to use binding with a dynamic Var.

Note

Dynamic Vars are typically named with so-called “earmuffs” (leading and trailing * characters) to indicate their dynamic nature. For instance, if you were going to call the Var dynamic-var, you’d actually name it *dynamic-var*.

Note

Dynamic Vars are never direct linked, so they are always subject to Var indirection. Users should be aware of this limitation when using dynamic Vars in hot paths.

Private Vars

Vars created with the ^:private metadata key are considered “private” within a namespace and access to those Vars from other namespaces is limited. Private Vars are not included by any require or refer operations and may not be referenced by using the fully-qualified symbol name of the Var either.

This is typically useful for cases where you might want to define an implementation function which you do not want to expose or export as a public API.

Warning

Since private Vars are not accessible outside of the namespace they are defined in, callers should take care not to use them in macro definitions since they will result in compile-time errors for users of the macro.

Unbound Vars

Vars defined without a value (as by (def some-var)) are considered “unbound”. Such Vars a root value defined which is different from nil and which only compares equal to itself and other unbound values referencing the same Var.