Comparing MTS LISP to modern Lisps

LISP is quite different from the languages we’ve looked at so far - but I will not give an introductory description, as there are plenty of resources on the Internet by better writers than me - instead I will look at how MTS LISP differs from more modern Lisps.

In the examples below, input is prefixed with a * and output with >.

Representation

MTS LISP assumes all keywords are in uppercase, but is otherwise free format. Do note that if you put the comment character ; in the first column this will be treated as a carriage control instruction to advance to the start of the next page in listings, so best prefix this with a space.

Types

As in other LISPs, the primitive data structure is the atom, which have values and property lists; atoms can be combined in s-expressions to form binary trees or lists. Here we define three atoms, a, b, c:

* (SETQ A 42)
>    42
* (SETQ B 'A)
>    A
* (SETQ C (CONS A B))
>    (42 . A)

There are no strings, but you can double-quote atom names to get printable output:

* (SETQ GREETING '"Hello, world!")
>    Hello, world!
* (PRINT GREETING)
>    Hello, world!

Arrays are simulated using functions: for a 2x2 array:

* (DEFINE (A ARRAY (2 2)))
>    A
* (SETA (A 1 2) 42)
>    42
* (PRINT (A 1 2))
>    42

Standard functions

Many familiar Lisp functions for list manipulation, arithmetic and control flow are available in this version; a brief sample:

* (SETQ L1 (LIST 1 2 (SUB 10 7)))
>       (1 2 3)
* (SETQ L2 (LIST 1 2 (SHIFT 2 2)))
>       (1 2 8)
* (CADR L2)
>       2
* (RPLACA L2 42)
>       (42 2 8)
* (DELETE 8 L2)
>       (42 2)
* L2
>       (42 2)
* (MAPC 'PRINT L2)
>       42
*       2
>       NIL

Functions and scope

As expected, you can use LAMBDA to define an anonymous function and DEFUN to attach a lambda function to an atom

* ((LAMBDA (A) (ADD A 2)) 40)
>       42

* (DEFUN FACTORIAL (N) (COND ((LESS N 2) 1)
*                            (T (TIMES N (FACTORIAL (SUB1 N))))))
>       FACTORIAL
* (FACTORIAL 5)
>       120

*LISP has dynamic scoping; variables defined in a function, or in a PROG, replace the current definitions for the life of the expression, including calls to other functions. See below, where the definition of X in the PROG is available in the GETX function.

* (SETQ X 42)
>       42
* (DEFUN GETX () X)
>       GETX
* (GETX)
>       42
* (PROG (X) (SETQ X 3) (GETX))
>       3
* (GETX)
>       42

There is no let or lexical binding in *LISP, hence no lexical closures. It may be possible to do dynamic closures but there does not seem to be function or funargs available which is needed to implement this.

There are also no macros (like defmacro) which can be expanded and evaluated at run time; there is a basic macro facility called READMACRO and PRINTMACRO where a function can be called whenever a designated atom is encountered while reading or printing.

GOTO in LISP

This rather blew my mind: a GOTO in LISP? The below will loop and read input until the user enters a number larger than 99. A and Z are labels:

(PROG (X)
A   (SETQ X (READ))
    (COND ((GREATER X 99) (GO Z))
          (T (GO A)))
Z   (PRINT 'DONE))

There is also (RETURN x), which will break out of the enclosing PROG immediately.

Super parentheses

Lisp is famously parenthesis-heavy: today we have text editors that can show matching pairs and indent code automatically but contemporary users of LISP would have had to do this by hand. To help out, *LISP defines the angle-brackets {} as super-parentheses: whenever an opening brace is encountered, the depth is remembered and when the matching close is found, the depth returns to that level. For example:

* (SETQ A (CAR (CDR (CDR (LIST 'A 'B 'C 'D)))))
>       C
* (SETQ A <CAR (CDR (CDR (LIST 'A 'B 'C 'D>)
>    C

Worlds

*LISP has a facility similar to database transactions where you can store the current state, change data and then either commit or rollback. (NEWWORLD) will return a ticket to the current state; you can then change data with special versions of LISP primitives such as SETQ2 or RPLACA2. (REALWORLD) can then be used to commit or (GETWORLD x) to roll back to the state at the time of ticket x.

* (SETQ2 A '42)
> NEWWORLD       04-15-82 RESTORED
>    42
* (SETQ W (NEWWORLD))
>    (((0 *UNDEF* . 42)) (NIL))
* (SETQ2 A 33)
>    33
* A
>    33
* (GETWORLD W)
>    NIL
* A
>    42

Further information

Chapter 3 of Successful Lisp is a good introduction to Lisp if you’ve not used it before; it’s written for Common Lisp users but most is applicable here.

Comments

comments powered by Disqus