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.8+. 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’s None.

  • Python does not offer different integer sizes, so short, int, and long are identical.

  • Python does not offer different precision floating point numbers, so double and float 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 typed Floating Point.

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

  • Numbers

    • Python integers natively support unlimited precision, so there is no difference between regular integers and those suffixed with N (which are read as BigInts in Clojure).

    • Floating point numbers are read as Python floats by default and subject to the limitations of that type on the current Python VM. Floating point numbers suffixed with M are read as Python Decimal types and support user-defined precision.

    • Ratios are supported and are read in as Python Fraction types.

    • Python natively supports Complex numbers. The reader will return a complex number for any integer or floating point literal suffixed with J.

  • Characters

    • Python does not support character types, so characters are returned as single-character strings.

  • Python data types

    • 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, and monitor-exit special forms are not implemented.

  • The Python VM specific await and yield 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 the basilisp.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 with basilisp. 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.

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 the new keyword is not required for constructing new objects in Python.

  • Python builtins are available under the special namespace python (as python/abs, for instance) without requiring an import.

See also

Python Interop

Type Hinting

Type hints may be applied anywhere they are supported in Clojure (as the :tag metadata key), 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

Compiler

Core Libraries

Basilisp includes ports of some of the standard libraries from Clojure which should generally match the source in functionality.