Special Forms

Special forms are the building blocks of any Lisp. Special forms are fundamental forms which offer functionality directly from the base distribution.

Primary Special Forms

special form(def name)
special form(def name expr)
special form(def name docstring expr)

Intern the value expr with the name name as a Var in the current namespace (the namespace pointed to by *ns* in the current thread).

name should be an unqualified symbol. If a namespace is included with the symbol, it will be silently discarded by the compiler. Metadata applied to the symbol name will be copied and applied to the interned Var. Common Var metadata applied via def include ^:private, ^:dynamic, and ^:redef.

expr may be any valid expression.

If no expression is expression is given, the Var is interned unbound.

If a docstring is provided, the value of the docstring will be accessible on the :doc key of the Var meta. Docstrings must be string literals. References to names or Vars containing strings will be cause a compile-time error.

(def my-var "Cool docstring!" :a-value)

(:doc (meta (var my-var)))  ;;=> "Cool docstring!"
(:doc (meta #'my-var))      ;;=> "Cool docstring!"

Note

By convention, def forms should only be used at the top level of a namespace file. While it is entirely legal to def a value within a function, the results of interning the Var within the function still apply to the current namespace. Within a function or method context, users should use the let special form to bind a value to a name in that scope.

special form(deftype name fields superclass+impls)

Define a new data type (a Python class) with the given set of fields which implement 0 or more Python interfaces and Basilisp protocols. Types defined by deftype are immutable, slotted classes by default and do not include any niceties beyond what a basic Python class definition would give you.

(defprotocol Shape
  (perimeter [self] "Return the perimeter of the Shape as a floating point number.")
  (area [self] "Return the area of the Shape as a floating point number."))

(deftype Rectangle [x y]
  Shape
  (perimeter [self] (+ (* 2 x) (* 2 y)))
  (area [self] (* x y)))

Fields should be given as a vector of names like a function argument list. Fields are accessible within implemented interface methods as unqualified names. Fields are immutable by default, but may be defined as mutable using the ^:mutable metadata. Mutable fields may be set using the set! special form from within any implemented interfaces. Fields may be given a default value using the {:default ...} metadata which will automatically be set when a new instance is created and which is not required to be provided during construction. Fields with defaults must appear after all fields without defaults.

Warning

Users should use field mutability and defaults sparingly, as it encourages exactly the types of design patterns that Basilisp and Clojure discourage.

Python interfaces include any type which inherits from abc.ABC. New types may also implement all Python “dunder” methods automatically, though may also choose to explicitly “implement” python/object. Python ABC types may include standard instance methods as well as class methods, properties, and static methods (unlike Java interfaces). Basilisp allows users to mark implemented methods as each using the ^:classmethod, ^:property, and ^:staticmethod metadata, respectively, on the implemented method name.

Neither the Python language specification nor the Python VM explicitly require users to use the abc.ABC metaclass and abc.abstractmethod decorator to define an abstract class or interface type, so a significant amount of standard library code and third-party libraries omit this step. As such, even if a class is functionally an abstract class or interface, the Basilisp compiler will not consider it one without abc.ABC in the superclass list. To get around this limitation, you can mark a class in the superclass list as “artificially” abstract using the ^:abstract metadata.

Warning

Users should use artificial abstractness sparingly since it departs from the intended purpose of the deftype construct and circumvents protections built into the compiler.

Note

deftype is certainly necessary at times, but users should consider using defrecord first. defrecord creates a record type, which behaves like a map but which can also implement Python interfaces and satisfy Basilisp protocols. This makes it an ideal for data which needs to interact with Python code and Basilisp code. Records are strictly immutable, however, so they may not be suitable for all cases.

special form(do)
special form(do & exprs)

Wrap zero or more expressions in a block, returning the result of the last expression in the block. If no expressions are given, return nil.

special form(fn name? [& args] & body)
special form(fn name? ([args1 args2] & body) ([args1 args2 & rest] & body))

Create a new anonymous function accepting zero or more arguments with zero or more body expressions. The result of calling the newly created function will be the final expression in the body, or nil if no body expressions are given.

Anonymous functions may optionally be given a name which should be an unqualified symbol. Function names may be useful in debugging as they will be used in stack traces.

Function arguments should be Symbols given in a vector. Functions may be defined with zero or more arguments. For functions with a fixed number of positional arguments, it is a runtime error to call a function with the wrong number of arguments. Functions may accept a variadic number of arguments (called “rest” arguments by convention) by terminating their argument list with & rest, with rest being any symbol name you choose. Rest arguments will be collected into a sequence which can be manipulated with the Basilisp sequence functions.

Note

Arguments in fn forms support Destructuring which is an advanced tool for accessing specific portions of arguments.

Functions may be overloaded with one or more arities (signature with different numbers of arguments). If a function has multiple arities, each arity should appear in its own list immediately after fn symbol or name if one is given.

Warning

All arities in a multi-arity function must have distinct numbers of arguments. It is a compile-time error to include two or more arities with the same number of arguments.

Warning

Multi-arity functions may only have zero or one arities which include a rest argument. It is a compile-time error to include multiple arities with rest arguments.

Warning

For multi-arity functions with a variadic arity, the variadic arity must have at least the same number of positional arguments as the maximum number of positional arguments across all of the remaining arities. It is a compile-time error to include a variadic arity in a multi-arity function with fewer fixed positional arguments than any other arity.

Note

Functions annotated with the :async metadata key will be compiled as Python coroutine functions (as by Python’s async def). Coroutine functions may make use of the await special form.

special form(if test true-expr)
special form(if test true-expr false-expr)

Evaluate the expression test, returning true-expr if test is truthy and false-expr otherwise. If no false-expr is given, it defaults to nil.

true-expr and false-expr may only be single expressions, so it may be necessary to combine if with do for more complex conditionals.

Note

In Basilisp, only nil and false are considered false by if – all other expressions are truthy. This differs from Python, where many objects may be considered falsey if they are empty (such as lists, sets, and strings).

See also

and, or, if-not, when, when-not

special form(. obj method)
special form(. obj method & args)
special form(. obj (method))
special form(. obj (method & args))
special form(.method obj)
special form(.method obj & args)

Call the method method of obj with zero or more arguments.

method must be an unqualified symbol.

Note

Methods prefixed with a - will be treated as property accesses .-, rather than method calls.

special form(.- obj attr)
special form(.-attr obj)

Access the attribute attr on object obj.

attr must be an unqualified symbol.

special form(let [& bindings] & body)

Bind 0 or more symbol names to the result of expressions and execute the body of expressions with access to those expressions. Execute the body expressions in an implicit do, returning the value of the final expression. As with do forms, if no expressions are given, returns nil.

Names bound in let forms are lexically scoped to the let body. Later binding expressions in let forms may reference the results of previously bound expressions. let form names may be rebound in child let and let forms.

Note

Bindings in let forms support Destructuring which is an advanced tool for accessing specific portions of arguments.

(let [])  ;;=> nil

(let [x 3]
  x)
;;=> 3

(let [x 3
      y (inc x)]
  y)
;;=> 4

Note

Names bound in let forms are not variables and thus the value bound to a name cannot be changed. let form bindings may be overridden in child let and letfn forms.

Note

Astute readers will note that the true “special form” is let*, while let is a core macro which rewrites its inputs into let* forms.

special form(letfn [& fns] & body)

Bind 0 or more functions to names and execute the body of expressions with access to those expressions. Execute the body expressions in an implicit do, returning the value of the final expression. As with do forms, if no expressions are given, returns nil.

Function names bound in letfn forms are lexically scoped to the letfn body. Functions in letfn forms may reference each other freely, allowing mutual recursion. letfn function names may be rebound in child let and letfn forms.

Note

Function definitions in letfn forms support Destructuring which is an advanced tool for accessing specific portions of arguments.

(letfn [])  ;;=> nil

(letfn [(plus-two [x] (+ (plus-one x) 1))
        (plus-one [x] (+ x 1))]
  (plus-two 3))
;;=> 4

Note

Names bound in letfn forms are not variables and thus the value bound to a name cannot be changed. letfn form bindings may be overridden in child let and letfn forms.

Note

Astute readers will note that the true “special form” is letfn*, while letfn is a core macro which rewrites its inputs into letfn* forms.

special form(loop [& bindings] & body)

loop forms are functionally identical to let forms, save for the fact that loop forms establish a recursion point which enables looping with recur.

(loop [])  ;;=> nil

(loop [x 3]
  x)
;;=> 3

(loop [x 1]
  (if (< x 10)
    (recur (* x 2))
    x))
;;=> 16

Note

loop forms will not loop automatically – users need to force the loop with recur. Returning a value (rather than recuring) from the loop terminates the loop and returns the final value.

Note

Astute readers will note that the true “special form” is loop*, while loop is a core macro which rewrites its inputs into let* forms.

special form(quote expr)

Return the forms of expr unevaluated, rather than executing the expression. This is particularly useful in when writing macros.

May also be shortened with the special character ', as 'form.

See also

Macros

special form(recur & args)

Evaluate the arguments given and re-binds them to the corresponding names at the last recursion point. Recursion points are defined for:

  • Each arity of a function created by fn (and by extension defn). The number arguments to recur must match the arity of the recursion point. You may not recur between different arities of the same function.

  • Loops created via loop. The arguments to recur are rebound to the names in the loop binding.

  • Methods defined on types created via deftype. Users should not pass the self or this reference to recur. recur is disallowed in static methods, class methods, and properties.

Note

All recursion with recur is tail-recursive by definition. It is a compile-time error to have a recur statement in non-tail position.

Recursion points are checked lexically, so recur forms may only be defined in the same lexical context as a construct which defines a recursion point.

Note

Recursion via recur does not consume an additional stack frame in any case. Python does not support tail-call optimization, so users are discouraged from looping using traditional recursion for cases with unknown bounds.

special form(reify superclass+impls)

Return a new object which implements 0 or more Python interfaces and Basilisp protocols. Methods on objects returned by reify close over their environment, which provides a similar functionality to that of a class created by deftype.

(defprotocol Shape
  (perimeter [self] "Return the perimeter of the Shape as a floating point number.")
  (area [self] "Return the area of the Shape as a floating point number."))

(defn rectangle [x y]
  (reify Shape
    (perimeter [self] (+ (* 2 x) (* 2 y)))
    (area [self] (* x y))))

Python interfaces include any type which inherits from abc.ABC. New types may also implement all Python “dunder” methods automatically, though may also choose to explicitly “implement” python/object. Python ABC types may include standard instance methods as well as class methods, properties, and static methods (unlike Java interfaces). Basilisp allows users to mark implemented methods as each using the ^:classmethod, ^:property, and ^:staticmethod metadata, respectively, on the implemented method name.

Neither the Python language specification nor the Python VM explicitly require users to use the abc.ABC metaclass and abc.abstractmethod decorator to define an abstract class or interface type, so a significant amount of standard library code and third-party libraries omit this step. As such, even if a class is functionally an abstract class or interface, the Basilisp compiler will not consider it one without abc.ABC in the superclass list. To get around this limitation, you can mark a class in the superclass list as “artificially” abstract using the ^:abstract metadata.

Warning

Users should use artificial abstractness sparingly since it departs from the intended purpose of the reify construct and circumvents protections built into the compiler.

See also

deftype

special form(set! target value)

Set the target to the expression value. Only a limited set of a targets are considered assignable:

Note

The Basilisp compiler makes attempts to verify whether a set! is legal at compile time, but there are cases which must be deferred to runtime due to the dynamic nature of the language. In particular, due to the non-lexical nature of dynamic Var bindings, it can be difficult to establish if a Var is thread-bound when it is set!, so this check is deferred to runtime.

special form(throw exc cause?)

Throw the exception named by exc. The semantics of throw are identical to those of Python’s raise statement with exception. Unlike Python’s raise, an exception is always required. A second optional cause exception may be provided after the exception to be thrown – this is a direct Basilisp equivalent to from semantics to Python’s raise statement. The cause may be nil to suppress cause chaining.

Note

Cause exceptions are stored in the __cause__ attribute on thrown exceptions. Contrast this with the case where during the handling of an exception a , a second exception b is raised. Without explicit chaining, a would be stored in the __context__ attribute of b. Standard Python exception formatting language will show both cause and context exceptions, but describes each differently. For more details, see Python’s documentation on exception context.

special form(try *exprs *catch-exprs finally?)

Execute 1 or more expressions (exprs) in an implicit do, returning the final value if no exceptions occur. If an exception occurs and a matching catch expression is provided, handle the exception and return the value of the catch expression. Evaluation of which catch expression to use follows the semantics of the underlying Python VM – that is, for an exception e, bind to the first catch expression for which (instance? ExceptionType e) returns true. Users may optionally provide a finally clause trailing the final catch expression which will be executed in all cases.

Note

Basilisp’s try special form matches the semantics of Python’s try with two minor exceptions:

  • In Basilisp, a single catch expression may only bind to a single exception type.

  • In Basilisp, the finally clause can never provide a return value for the enclosing function.

special form(var var-name)

Access the Var named by var-name. It is a compile-time exception if the Var cannot be resolved.

May also be shortened to the reader macro #'.

#'my-var

Basilisp-specific Special Forms

The special forms below were added to provide direct support for Python VM specific features and their usage should be relegated to platform-specific code.

special form(await expr)

Await a value from a function as by Python’s await expression. Use of the await is only valid for functions defined as coroutine functions. See fn for more information.

special form(yield)
special form(yield expr)

Yield a value from a function as by Python’s yield statement. Use of the yield form automatically converts your function into a Python generator. Basilisp seq and sequence functions integrate seamlessly with Python generators.