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
exprwith the namenameas a Var in the current namespace (the namespace pointed to by*ns*in the current thread).nameshould be an unqualified symbol. If a namespace is included with the symbol, it will be silently discarded by the compiler. Metadata applied to the symbolnamewill be copied and applied to the interned Var. Common Var metadata applied viadefinclude^:private,^:dynamic, and^:redef.exprmay 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
:dockey 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,
defforms should only be used at the top level of a namespace file. While it is entirely legal todefa 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 theletspecial 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
deftypeare 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
^:mutablemetadata. 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. PythonABCtypes 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^:staticmethodmetadata, respectively, on the implemented method name.Neither the Python language specification nor the Python VM explicitly require users to use the
abc.ABCmetaclass andabc.abstractmethoddecorator 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.ABCin the superclass list. To get around this limitation, you can mark a class in the superclass list as “artificially” abstract using the^:abstractmetadata.Warning
Users should use artificial abstractness sparingly since it departs from the intended purpose of the
deftypeconstruct and circumvents protections built into the compiler.Note
deftypeis certainly necessary at times, but users should consider usingdefrecordfirst.defrecordcreates 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
nilif 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, withrestbeing 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
fnforms 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
fnsymbol 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-expriftestis truthy andfalse-exprotherwise. If nofalse-expris given, it defaults tonil.true-exprandfalse-exprmay only be single expressions, so it may be necessary to combineifwithdofor 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
methodofobjwith zero or more arguments.methodmust 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
attron objectobj.attrmust 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 withdoforms, if no expressions are given, returnsnil.Names bound in
letforms are lexically scoped to theletbody. Later binding expressions inletforms may reference the results of previously bound expressions.letform names may be rebound in childletandletforms.Note
Bindings in
letforms 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
letforms are not variables and thus the value bound to a name cannot be changed.letform bindings may be overridden in childletandletfnforms.Note
Astute readers will note that the true “special form” is
let*, whileletis 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 withdoforms, if no expressions are given, returnsnil.Function names bound in
letfnforms are lexically scoped to theletfnbody. Functions inletfnforms may reference each other freely, allowing mutual recursion.letfnfunction names may be rebound in childletandletfnforms.Note
Function definitions in
letfnforms 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
letfnforms are not variables and thus the value bound to a name cannot be changed.letfnform bindings may be overridden in childletandletfnforms.Note
Astute readers will note that the true “special form” is
letfn*, whileletfnis a core macro which rewrites its inputs intoletfn*forms.
- special form(loop [& bindings] & body)¶
loopforms are functionally identical toletforms, save for the fact thatloopforms 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
loopforms will not loop automatically – users need to force the loop withrecur. Returning a value (rather thanrecuring) from the loop terminates the loop and returns the final value.Note
Astute readers will note that the true “special form” is
loop*, whileloopis a core macro which rewrites its inputs intolet*forms.
- special form(quote expr)¶
Return the forms of
exprunevaluated, 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 torecurmust 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 theloopbinding.Methods defined on types created via
deftype. Users should not pass theselforthisreference torecur.recuris disallowed in static methods, class methods, and properties.
Note
All recursion with
recuris tail-recursive by definition. It is a compile-time error to have arecurstatement in non-tail position.Recursion points are checked lexically, so
recurforms may only be defined in the same lexical context as a construct which defines a recursion point.Note
Recursion via
recurdoes 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
reifyclose 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. PythonABCtypes 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^:staticmethodmetadata, respectively, on the implemented method name.Neither the Python language specification nor the Python VM explicitly require users to use the
abc.ABCmetaclass andabc.abstractmethoddecorator 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.ABCin the superclass list. To get around this limitation, you can mark a class in the superclass list as “artificially” abstract using the^:abstractmetadata.Warning
Users should use artificial abstractness sparingly since it departs from the intended purpose of the
reifyconstruct and circumvents protections built into the compiler.See also
- special form(set! target value)¶
Set the
targetto the expressionvalue. Only a limited set of a targets are considered assignable:deftypelocals designated as:mutableDynamic 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 ofthroware 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 tofromsemantics to Python’sraisestatement. The cause may benilto 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 exceptionbis raised. Without explicit chaining,awould 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 0 or more expressions (
exprs) in an implicitdo, returning the final value if no exceptions occur ornilif no expressions were given. If an exception occurs and a matchingcatchexpression is provided, handle the exception and return the value of thecatchexpression ornilif no expression is given. Evaluation of whichcatchexpression to use follows the semantics of the underlying Python VM – that is, for an exceptione, bind to the firstcatchexpression for which(instance? ExceptionType e)returnstrue. Users may optionally provide afinallyclause trailing the finalcatchexpression which will be executed in all cases.Note
Basilisp’s
tryspecial form matches the semantics of Python’s try with two minor exceptions:In Basilisp, a single
catchexpression may only bind to a single exception type.In Basilisp, the
finallyclause 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.