TXR Lisp

TXR Lisp is an innovative, original dialect from the Lisp language family. It is not an implementation of Common Lisp, Scheme or any existing dialect.

One goals of TXR Lisp is to remove verbosity from Lisp programming, where doing so does not conflict with the Lisp ideology ("Lispiness"). This means that the primary means of making programs succinct are semantic devices, rather than syntactic sugars. However,  sugars are also employed, when they are not disruptive to the surrounding syntax.

Another goal of TXR Lisp is to serve as a platform for experimentation an advancement within the context of a Lisp paradigm, and bring together the right mix of advanced features as well as innovative new solutions to old problems, integrating these into an ergonomic whole which can be applied to real, practical tasks, right now.

Imperative to be Functional

TXR Lisp supports functional programming, but isn't a functional language. The TXR project consciously rejects the purely functional ideology. TXR Lisp supports programming with higher order functions by providing

TXR Lisp supports imperative programming directly. It has mutable variables, and mutable data structures such as lists, vectors, strings and structures. It has control constructs such as loops, exception handling and non-local exits.

Strictly Eager, with a Lazy Streak

TXR Lisp is strictly evaluated. Function argument expressions are evaluated in left to right order, before the function call takes place. Special operators and macros evaluate forms in a predictable order. Sequencing operations with visible effects such as I/O is easy, just like in any mainstream imperative language. TXR Lisp, however, has support for lazy evaluation via several features:

TXR Lisp's construct for explicit laziness in the middle of a strict language provide most of the benefits of lazy evaluation, without the drawbacks such as poor performance, confusing semantics and clumsy coordination of side effects which plague lazily-evaluated functional languages.

Generic Brand

Unlike in mainstream Lisp dialects, TXR Lisp allows traditional list operations like car, cdr, and mapcar to be applied to vectors and character strings, which is very expressive and useful.

Lisp-1,-2 Under one Roof

A classic dilemma in the design of a Lisp dialect is whether to make it Lisp-1 or Lisp-2. That is to say, should function and variable bindings be in one namespace or in separate namespaces? TXR Lisp innovates in this area with a new solution which integrates both styles. The underlying infracstructure is Lisp-2: the global and lexical environments have function and variable namespaces. However, Lisp-1 style evaluation of arguments (with the two namespaces apparently folded into one) occurs in forms which are written using square brackets instead of parentheses. This feature is deeply integrated into the language; it cannot be implemented in Lisp-2 dialects to the same level without working at the implemenation level, or else transforming entire top-level forms with a code walker, or else making it an incomplete hack. Even the macro expander is aware of the feature: when a reference to a lexical function occurs as a Lisp-1 style argument, that function shadows a symbol macro in an outer scope, which would not be shadowed in a Lisp-2 form due to the symbol macro being considered in a variable namespace.

Programming with functional arguments ("higher order functions") in TXR Lisp is free of distracting noise like funcall and #'. The funcall function exists and is named call, and the function operator is called fun; but these are hardly ever seen. The Common Lisp #' (hash quote) notation is absent. The lambda operator in  TXR Lisp  works directly; it isn't a macro which  expands to  (fun (lambda ...)).   Yet, TXR Lisp retains the advantages of Lisp-2, such as its natural view of macro programming and referential hygiene.

Incidentally, the square bracket forms in TXR Lisp provide a purer vision of Lisp-1 than Lisp-1 dialects themselves. Proponents of Lisp-1 dialects like to say that every position of a form is evaluated in the same way, which is more consistent than Lisp-2 which treats the operator specially. Unfortunately, that is a lie, because any worthwhile Lisp-1 dialect has macros. The leftmost position of a form in any worthwhile Lisp-1 dialect must be considered in the macro namespace. By contrast, TXR Lisp's square bracket forms cannot be macro forms. In the form [a b c],  the  symbol a must resolve to a function, or an object which can be used as a function. It cannot be a macro (other than a symbol macro which simply replaces a with another form). So the Lisp-1 advocacy soundbite is, ironically, true in TXR Lisp: the arguments of a square bracketed form are all evaluated the same way, period.

Ask the Boss for Arrays

Programmers who encounter one of the major Lisp dialects for the first time usually complain about the clumsy support for working with arrays. TXR Lisp takes heed of these complains. TXR's square bracket forms provide array indexing and range referencing.

Indexing works naturally because sequences (lists, vectors and arrays) as well as hashes are considered functions which map indexes to elements. For instance, the form (mapcar "abc" #(2 0 1)) produces the vector #(#\c #\a #\b) because the character string "abc" is a function which maps the indices 0, 1 and 2 to the character objects #\a, #\b and #\c. (Also note that a vector is being processed with mapcar, which is why the type which emerges is a vector).  Therefore, the square brackets Lisp-1 notation provides array indexing. The form ["abc" 1]  means "call the "abc" string as a function, passing it the argument 1". The effect is that the character #\b is retrieved.  This works for lists and vectors in the same way. For hash tables, a hash lookup is performed. If h is a hash table,  and k is a key, then [h k] performs a lookup. Also, [h k v] performs a lookup such that v is substituted if k is not found.

Range indexing is supported using the dotdot range notation. For instance [a 2..5] denotes the slice of a starting from element [a 2] up to and including [a 4], excluding [a 5].  If the symbol t is used as the upper endpoint, it denotes the element one beyond the last; in other words, the slice extends to the end. The colon symbol : can also be used on either end to denote "from the start" or "to the end". All of the following forms denote a slice of a a which includes all of a[a :..:], [a 0..:], [a 0..t], [a :..t].   Negative indices are supported, so that -1 denotes the last element. The expression [a 0..-1] calculates a slice of a which excludes the last element.  The dotdot notation is a syntactic sugar, which denotes the construction of a range object: a..b is converted by TXR Lisp's parser to (rcons a b), a call to the rcons function which constructs a range object.

Both element and range indexing forms support assignment as well as deletion (if the array-like object is stored in an assignable place), which makes for flexible and succinct array editing. For instance, if variable a holds the string "car", then (set [a 1..2] "ape") changes a  to "caper". The following example shows how we can exchange the ranges of two arrays in a single swap operation:

(let ((a "archibald") (b "spooner"))
(swap [a 2..4] [b 0..2])
(list a b))

-> ("arspibald" "chooner")

Note that although the exchanged ranges happen to be of equal length in the example, that isn't a constraint.

Strings Attached

Direct interpolation of values into strings is a considerable convenience in programming languages. Their use leads to succinct, expressive code for string construction. Mainstream Lisp dialects are missing the boat in this regard. TXR Lisp has better designed string interpolation than most scripting languages.

Interpolated string literals are called "quasiliterals" in TXR Lisp, and are delimited by backquotes rather than double quotes. TXR Lisp avoids the mistaken design of featuring just one kind of string literal, which supports interpolation. There is a need for strings which are true literals.

In a backquoted string, the @ character denotes the insertion of the value of an expression. It is followed by an expression directly, or an expression surrounded in curly braces.  The curly brace notation solves certain ambiguity problems which arise, and also allows for the expression of modifiers for expressing field width, left or right alignment and a separator string for merging list elements. Example:

(defvarl str "abc")
(defvarl words '#"how now brown cow")
(prinl `@str-@str`)
(prinl `@{str 10}-@{str -10}`)
(prinl `Words: @{words ", " -40}`)

Output:

"abc-abc"
"abc - abc"
"Words: how, now, brown, cow"

Both quasiliterals and regular string literals can be prefixed by # which denotes a word list. In the above example, #"how now brown cow"   denotes the list structure ("how" "now" "brown" "cow"). This is prefixed with a quote to express the quoted list '("how" "now" "brown" "cow"). The ommission of the quote is necessary because word lists can be embedded in unevaluated structure. However, quasiliterals are structures intended for evaluation and so the expression #`how now brown cow` evaluates to the list ("how" "now" "brown" "cow") without the need for a quote. Quasiliteral syntax produces code which, when evaluated, constructs the implied character string or list of strings, whereas an ordinary literal denotes a string or list of strings as program syntax.

Objectively Simple

TXR Lisp doesn't have an object system similar to Common Lisp's CLOS. Rather, TXR Lisp's structures make up a simple object system featuring single dispatch and multiple inheritance.

The term "class" is avoided in TXR Lisp; rather it has structures (structs) which have object-oriented features. Struct types must be explicitly defined using the defstruct macro, or else the underlying API that it uses. Structs have both instance slots and static slots. Under inheritance, a static slot can be overriden with an instance slot or vice versa. Methods are represented as function values stored in static slots, and therefore methods "belong" to structs. Dispatch of a method named m on an object instance o takes place by dispatching the function stored in the slot named m of o. The function receives the object as its leftmost argument, followed by the remaining method arguments. New static slots can be added to an existing type. Also, a type which has an inherited static slot can break that relationship and get its own non-inherited instance of the static slot. This is the basis for method definitions and redefinitions outside of defstruct.

TXR Lisp features a dot notation for referring to struct slots, including method slots. For instance sim.start-time.(set 42) means to retrieve the slot start-time of the sim structure, and then invoke the set method on that start-time. This notation is a syntactic sugar for the Lisp syntax (qref sim start-time (set 42)). The qref symbol has a macro binding; that macro compiles the abstract syntax into the slot references and function calls that it denotes.

Suppose we have a list of objects, and we'd like to call set on their respective start-time members to reset their times to zero:

(mapdo .start-time.(set 42) obj-list)

Here, the dot notation begins with a leading dot. This variant of the notation doesn't correspond to qref but to uref (unbound reference): (uref start-time (set 42)). This uref form compiles into a higher order function which takes an object as an argument. That function references the object's start-time slot and calls the set method on it.

Objects have methods called on construction, as well as finalizers. When the garbage collector detects an unreachable object whose finalizers have not yet been called, the finalizers are called at that time. An object's finalizers are also called if an exception occurs during its construction, and may also be called explicitly before the object's lifetime ends via the call-finalizers function. The with-objects macro instantiates objects in a lexical scope, and calls their finalizers when their scope ends, enabling some aspects of the "RAII" idiom from the C++ language to be used in TXR Lisp.