https://www.nongnu.org/txr/txr-lisp.html
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
* first-class functions (lexical closures);
* an expressive syntax for explicit partial evaluation;
* a significant standard library of userful functional combinators;
* non-function objects which can be treated as functions; and
* delimited continuations, for reifying program state as a
function.
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:
* lazy lists based on lazy cons cell objects, deeply integrated
into the Lisp data model;
* lazy instantiations OOP structures; allowing "one step" creation
of infinite, lazy structures made up of objects, and mutually
referencing object clusters.
* explicit delay and force operators; and
* a self-referential binding operator mlet, which is an advancement
over Scheme's letrec.
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.