Lisp

From Esolang
Jump to navigation Jump to search

This article deals with Lisp as it relates to esoteric programming. For more general information, see the Wikipedia article on Lisp.

Lisp is a language (but not an esoteric language, just a regular language (or more accurately family of languages (but particularly one single language called Lisp (a.k.a. Common Lisp) that has stuck around these days (and a few other modern implementations such as Scheme, Clojure, newLISP, Emacs Lisp and Hy)) where the language family's members all have lots of brackets, prefix notation and homoiconicity) no matter what it may seem at times) first invented in 1958. It has been criticised for it's "excessive" and "infernal" parenthesisation of almost every part of the source code (at times claimed to be "such that all substantial programs end in vast swathes of ), oceans so wide and featureless that the careless reader loses track of both shores" (and indeed one may find before long that they seem to have been turned around by the disorientation: the ) have become ( and one is simply venturing further into the nesting)) but Lisp is also lauded for pioneering many now-ubiquitous features (including (but not limited to) the REPL, recursion, bootstraps and dynamic typing) of modern programming languages (in large part because of homoiconicity (i.e. where code is represented as data that can be processed) that allows Lisp programs to easily transform other programs (making new features of the language much easier to implement)).

Prefix notation

Lisp code is structured as a list of expressions, where those expressions are also lists. This structure is recursive; it's lists almost all the way down. Each list begins with a function name as its first element — allowed because lists are dynamically typed, and functions are first class citizens. The rest of the list is the arguments that function should be called with. Both the arguments and the function itself can be lists, in which case they are evaluated recursively until the function call is fully specified.

For example (if (> 1 2) "weird" "sane") calls the if function (well, macro technically, but that's inconsequential) on a condition, (> 1 2); a true branch, returning "weird"; and a false branch, evaluating to "sane" instead. The (> 1 2) subexpression is another list, beginning with the greater-than function. An infix equivalent would be 1 > 2, so it works out to false.

Homoiconicity

Lisp was originally called LISP, short for LISt Processor, a language equipped for managing linked lists. Crucially, its abstract syntax tree forms nested lists. This makes it trivial to create programs that operate on Lisp programs. As nested sequences of values are also an excellent analogue for many other source code formats, this power also allows Lisp an easier time writing metaprograms for other languages. In Lisp, no programmer should be scared of the eval command when it's necessary, a practice non-Lispers would consider unnatural.

Brackets

While brackets in Lisp are very easy to mock for non-Lispers, because they are the first thing you notice about the source code, they are a powerful tool for delimiting anything, and allow the language's syntax to be trivial to parse, for readers and programs alike. All lists, and by extension all code, is simply whatever is between the brackets.

List processing

Lisp uses three main tools for list processing. cons is a function that constructs a "cons cell", which is a pair of two elements. (cons 1 2) would be shown as (1 . 2). Lists are linked lists of pairs, made from cons cells, so consing onto a list adds the consed item onto the front of the list. Lists are defined as either the empty list, () or '(), or (cons X L) where L is a list and X is anything. After constructing a list, deconstruction uses the car and cdr functions, whose original names "Contents of Address Register" and "Contents of Data Register" are now meaningless. (car (a . b)) is a, whereas for cdr (pronounced COULD-er /ˈkʊdər/) it would give b. Since lists are made of pairs, car gives the first element of a list, and cdr gives the entire list excluding the first element.

These functions give rise to an idiom common in recursive functional programming for processing an entire list without cumbersome loops: stop if the list is empty; or process the first element only, then call this same function on the rest of the list.