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 namename
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 symbolname
will be copied and applied to the interned Var. Common Var metadata applied viadef
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 todef
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 thelet
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 theset!
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
. PythonABC
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 andabc.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 withoutabc.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 usingdefrecord
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.See also
- 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
, withrest
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.
- special form(if test true-expr)¶
- special form(if test true-expr false-expr)
Evaluate the expression
test
, returningtrue-expr
iftest
is truthy andfalse-expr
otherwise. If nofalse-expr
is given, it defaults tonil
.true-expr
andfalse-expr
may only be single expressions, so it may be necessary to combineif
withdo
for more complex conditionals.
- 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
ofobj
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.See also
- special form(.- obj attr)¶
- special form(.-attr obj)¶
Access the attribute
attr
on objectobj
.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 withdo
forms, if no expressions are given, returnsnil
.Names bound in
let
forms are lexically scoped to thelet
body. Later binding expressions inlet
forms may reference the results of previously bound expressions.let
form names may be rebound in childlet
andlet
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 childlet
andletfn
forms.Note
Astute readers will note that the true “special form” is
let*
, whilelet
is a core macro which rewrites its inputs intolet*
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 withdo
forms, if no expressions are given, returnsnil
.Function names bound in
letfn
forms are lexically scoped to theletfn
body. Functions inletfn
forms may reference each other freely, allowing mutual recursion.letfn
function names may be rebound in childlet
andletfn
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 childlet
andletfn
forms.Note
Astute readers will note that the true “special form” is
letfn*
, whileletfn
is a core macro which rewrites its inputs intoletfn*
forms.
- special form(loop [& bindings] & body)¶
loop
forms are functionally identical tolet
forms, save for the fact thatloop
forms establish a recursion point which enables looping withrecur
.(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 withrecur
. Returning a value (rather thanrecur
ing) from the loop terminates the loop and returns the final value.Note
Astute readers will note that the true “special form” is
loop*
, whileloop
is a core macro which rewrites its inputs intolet*
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
- 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 extensiondefn
). The number arguments torecur
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 theloop
binding.Methods defined on types created via
deftype
. Users should not pass theself
orthis
reference torecur
.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 arecur
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 bydeftype
.(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
. PythonABC
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 andabc.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 withoutabc.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
- special form(set! target value)¶
Set the
target
to the expressionvalue
. Only a limited set of a targets are considered assignable:deftype
locals designated as:mutable
Dynamic Vars with established thread-local bindings
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 isset!
, so this check is deferred to runtime.
- special form(throw exc)¶
- special form(throw exc cause)
Throw the exception named by
exc
. The semantics ofthrow
are identical to those of Python’s raise statement with exception. Unlike Python’sraise
, 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 tofrom
semantics to Python’sraise
statement. The cause may benil
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 exceptiona
, a second exceptionb
is raised. Without explicit chaining,a
would be stored in the__context__
attribute ofb
. 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 implicitdo
, returning the final value if no exceptions occur. If an exception occurs and a matchingcatch
expression is provided, handle the exception and return the value of thecatch
expression. Evaluation of whichcatch
expression to use follows the semantics of the underlying Python VM – that is, for an exceptione
, bind to the firstcatch
expression for which(instance? ExceptionType e)
returnstrue
. Users may optionally provide afinally
clause trailing the finalcatch
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.