Table of Contents
Introduction
As I read Steele's Common Lisp: The Language I had a strong sense of the way in which Common Lisp was developed in the context of Lisp machines. It makes for an interesting way of thinking about computing languages: as a way of using the computer rather than as a means for writing programs. The parallel I've been drawing is with Emacs Lisp primarily as a way of editing text (rather than writing programs).
There is another parallel to be drawn with command shells as ways of using a computer. It seems weaker. Compared with Common Lisp's explicit design process, Bash, MSDOS, and other shells are more ad hoc aggregations of utilities rather than formal designs: grep
& find
& cat
& ls
& cd
don't share a strong coherance. Knowing the options of one does not create strong intuitions into the others.
As I dug deeper into the Common Lisp: The Language the consistent construction of various sequence
and list
functions, it became apparent that this was a byproduct of the Lisp Machine context. Consistent user interface design is orthogonal to the form of the user interface. Command line, GUI, or programming language all have the same requirements.
The idea of data structures such as list
or sequence
living in user space feels radical today. But it seems simply a byproduct of decades of WIMP dominance – years of familiarity and ad hockery means Bash pipes or Perl regex's feel normal parts of user space.
Basic Semantics
Destructive versus Non-Destructive
Matching value versus applied predicate
List functions ending
Definition
<<list-append-import>> <<operators-import>> # Helpers <<identity>> <<operators>> # List Basics <<cons>> <<first>> <<rest>> <<list-append>> # Non Destructive List functions <<copy-list>> <<remove>>
Helpers
Operators
Python does not allow passing operators such as <
or +
as functions. Instead it provides the operators module which is included here to allow tests to be passed to list functions.
import operator
less_than = operator.lt lt = operator.lt less_than_or_equal = operator.le le = operator.le equal = operator.eq eq = operator.eq non_equal = operator.ne ne = operator.ne greater_than_or_equal = operator.ge ge = operator.ge
Identity
Python does not have an identity function. Including one is not considered Pythonic and writing one's own is considered the Python way.
def identity(x): return x
List Basics
cons
Where to start but with cons
. It's not that Python doesn't have the ability to add to lists, it's just that trying to translate from front to rear addition is probably not the best place to start.
def cons (element, a_list): """ Adds an elment to the *front* of a_list. If a_list is not a list, cons creates a list of one element holding a_list. """ if not type(a_list) == list: a_list = [a_list] a = [element] a.extend(a_list) return a
first
def first(a_list): """ Returns the first elment of a list. Returns False if the list is empty. """ if len(a_list)==0: return False else: return a_list[0]
rest
def rest(a_list): """ Returns a list minus its first elment. Returns false if list is empty. Returns the empty list if list has one element. """ if len(a_list)==0: return False elif len(a_list)==1: return [] else: return a_list[1:]
list_append
This is a rewrite of an earlier version that was written for gps_py that only took two lists. Python already has an append
function.
def list_append(list_1, list_2): return list_1 + list_2
from functools import reduce as reduce
def list_append(*args): return reduce(list.__add__, args)
Non-Destructive
copy_list
To me, copy-list
is where the idea of Common Lisp as a language for users really hit home. There were many idioms for copying a list in other lisp dialects. By abstracting over those, copy-list
provides an explicit mechanism for users to express an intent to bypass destructive semantics.
def copy_list(ls): return [i for i in ls]
local_iterate
This is a function designed to work as a local closure within other functions. It replaces list comprehensions in order to implement the _count
property.
def iterate(): c = 0 ret = [] for i in seq: if c < _count: if test(key(i),item): c += 1 else: ret += [i] else: ret += [i] return ret
The version when test_not
is True
.
def iterate_not(): c = 0 ret = [] for i in seq: if c < _count: if not test(key(i),item): c += 1 else: ret += [i] else: ret += [i] return ret
remove
def remove(item, sequence, from_end=False, test=equal, test_not=False, start=None, end=None, count=False, key=identity): # chop the front off the sequence if only # using part of it if start: front = sequence[:start] else: front = [] # chop the rear off the sequence if only # using part of it if end: rear = sequence[rear:] else: rear = [] # make a copy of the sequence for local mutation # and cut to length seq = copy_list(sequence)[start:end] # reverse the target sequence if requested if from_end: seq = seq.reverse() # If there's no count, let it be seq length if not count: _count = len(seq) else: _count = count # Add the iterate closure <<local-iterate>> # Add iterate_not closure <<local-terate-not>> # Main logic if not test_not: middle = iterate() # return values in the same order if from_end: middle.reverse() return list_append(front,middle,rear) else: middle = iterate_not() # return values in the same order if from_end: middle.reverse() return list_append(front,middle,rear)
remove_if
def remove_if(pred, seq): return [i for i in pred if not pred(i)]