Differences from Clojure¶
Basilisp strives to be roughly compatible with Clojure, but just as ClojureScript diverges from Clojure at points, so too does Basilisp. Being a hosted language like Clojure (which celebrates its host, rather than hiding it) means that certain host-specific constructs cannot be replicated on every platform. We have tried to replicate the behavior of Clojure as closely as we can while still staying true to Python.
This document outlines the major differences between the two implementations so users of both can understand where Basilisp differs and adjust their code accordingly. If a feature differs between the two implementations and it is not stated here, please first check if there is an open issue on GitHub to implement or align the feature with Clojure or to clarify if it should be omitted.
Hosted on Python¶
Unlike Clojure, Basilisp is hosted on the Python VM. Basilisp supports versions of Python 3.9+. Basilisp projects and libraries may both import Python code and be imported by Python code (once the Basilisp runtime has been initialized and the import hooks have been installed).
Type Differences¶
nil
corresponds to Python’sNone
.Python does not offer different integer sizes, so
short
,int
, andlong
are identical.Python does not offer different precision floating point numbers, so
double
andfloat
are identical.Type coercions generally delegate to the relevant Python constructor, which handles such things natively.
Collections
Sorted sets, sorted maps, and array maps are not implemented (support is tracked in #416).
Arithmetic Comparison¶
Basilisp, in contrast to Clojure, does not distinguish between integer (int
) and floating point (float
) as separate categories for equality comparison purposes where the =
comparison between any int
and float
returns false
. Instead, it adopts Python’s =
comparison operator semantics, where the int
is optimistically converted to a float
before the comparison. However, beware that this conversion can lead to certain caveats in comparison where in rare cases seemingly exact int
and float
numbers may still compare to false
due to limitations in floating point number representation.
In Clojure, this optimistic equality comparison is performed by the ==
function. In Basilisp, ==
is aliased to behave the same as =
.
Note
Basilisp’s =
will perform as expected when using Python decimal.Decimal
typed floating-point numbers.
See also
Python’s floating point arithmetic documentation
Concurrent Programming¶
Python is famous for it’s Global Interpreter Lock limiting performance in the multi-core case. As such, users may call into question the value of Clojure’s concurrency-focused primitives in a single-threaded context. However, ClojureScript’s own “Differences from Clojure” document puts its best:
Clojure’s model of values, state, identity, and time is valuable even in single-threaded environments.
That said, there are some fundamental differences and omissions in Basilisp that make it differ from Clojure.
Atoms work just as in Clojure.
Basilisp does not include Ref types or software transactional memory (STM) support.
Basilisp does not include Agent support (support is tracked in #413).
All Vars are reified at runtime and users may use the
binding
macro as in Clojure.Non-dynamic Vars are compiled into Python variables and references to those Vars are made using Python variables using Direct Linking.
Vars are created in all cases, but only used in certain cases.
Reader¶
-
Python integers natively support unlimited precision, so there is no difference between regular integers and those suffixed with
N
(which are read asBigInt
s in Clojure).Floating point numbers are read as Python
float
s by default and subject to the limitations of that type on the current Python VM. Floating point numbers suffixed withM
are read as Pythondecimal.Decimal
types and support user-defined precision.Ratios are supported and are read in as Python
fractions.Fraction
types.Python natively supports Complex numbers. The reader will return a complex number for any integer or floating point literal suffixed with
J
.
-
Python does not support character types, so characters are returned as single-character strings.
-
The reader will return the native Python data type corresponding to the Clojure type in functionality if the value is prefixed with
#py
.
Regular Expressions¶
Basilisp regular expressions use Python’s regular expression
syntax and engine.
REPL¶
Basilisp’s REPL experience closely matches that of Clojure’s.
Evaluation¶
Basilisp code has the same evaluation semantics as Clojure.
The load
and load-file
functions are supported though their usage is generally discouraged.
Basilisp does not perform any locals clearing.
Special Forms¶
Basilisp special forms should be identical to their Clojure counterparts unless otherwise noted below.
def
does not support the^:const
metadata key.if
does not use any boxing behavior as that is not relevant for Python.The JVM specific
locking
,monitor-enter
, andmonitor-exit
special forms are not implemented.The Python VM specific
await
andyield
forms are included to support Python interoperability.
Namespaces¶
Basilisp namespaces are reified at runtime and support the full set of clojure.core
namespace APIs.
Namespaces correspond to a single Python module which is where the compiled code (essentially anything that has been def
-ed) lives.
Users should rarely need to be concerned with this implementation detail.
As in Clojure, namespaces are bootstrapped using the ns
header macro at the top of a code file.
There are some differences between ns
in Clojure and ns
in Basilisp:
Users may use
:refer-basilisp
and:refer-clojure
interchangeably to control which of thebasilisp.core
functions are referred into the new namespace.Prefix lists are not supported for any of the import or require selectors.
Automatic namespace aliasing: if a namespaces starting with
clojure.
is required and does not exist, but a corresponding namespace starting withbasilisp.
does exist, Basilisp will import the latter automatically with the former as an alias.
Libs¶
Support for Clojure libs is planned.
basilisp.core¶
basilisp.core/int
coerces its argument to an integer. When given a string input, Basilisp will try to interpret it as a base 10 number, whereas in Clojure, it will return its ASCII/Unicode index if it is a character (or fail if it is a string).basilisp.core/float
coerces its argument to a floating-point number. When given a string input, Basilisp will try to parse it as a floating-point number, whereas Clojure will raise an error if the input is a character or a string.basilisp.core/alter-var-root
: updates to a Var’s root via this function may not reflect in code that directly references the Var unless the Var is marked with^:redef
metadata or declared as a dynamic variable. This is due to the Direct Linking Optimization and differs with Clojure where such changes are always visible.
Refs and Transactions¶
Neither refs nor transactions are supported.
Agents¶
Agents are not currently supported. Support is tracked in #413.
Host Interop¶
Host interoperability features generally match those of Clojure.
new
is a macro for Clojure compatibility, as thenew
keyword is not required for constructing new objects in Python.Python builtins are available under the special namespace
python
(aspython/abs
, for instance) without requiring an import.The qualified constructor form
Classname/new
introduced in Clojure 1.12 is not supported, becausenew
is a valid Python method identifier unlike in Java.Qualified methods may be referenced with or without a leading
.
character regardless of whether they are static, class, or instance methods.
See also
Type Hinting¶
Type hints may be applied anywhere they are supported in Clojure (as the :tag
or :param-tags
metadata keys), but the compiler does not currently use them for any purpose.
Tags provided for def
names, function arguments and return values, and let
locals will be applied to the resulting Python AST by the compiler wherever possible.
Particularly in the case of function arguments and return values, these tags maybe introspected from the Python inspect
module.
There is no need for type hints anywhere in Basilisp right now, however.
Compilation¶
Basilisp’s compilation is intended to work more like Clojure’s than ClojureScript’s, in the sense that code is meant to be JIT compiled from Lisp code into Python code at runtime.
Basilisp compiles namespaces into modules one form at a time, which brings along all of the attendant benefits (macros can be defined and immediately used) and drawbacks (being unable to optimize code across the entire namespace).
gen-class
is not required or implemented in Basilisp, but gen-interface
is.
Users may still create dynamic classes using Python’s type
builtin, just as they could do in Python code.
See also
Core Libraries¶
Basilisp includes ports of some of the standard libraries from Clojure which should generally match the source in functionality.
basilisp.data
is a port ofclojure.data
basilisp.edn
is a port ofclojure.edn
basilisp.io
is a port ofclojure.java.io
basilisp.set
is a port ofclojure.set
basilisp.shell
is a port ofclojure.java.shell
basilisp.stacktrace
is a port ofclojure.stacktrace
basilisp.string
is a port ofclojure.string
basilisp.test
is a port ofclojure.test
basilisp.walk
is a port ofclojure.walk