home |
syllabus |
src |
submit |
chat |
©2019
by Tim Menzies
- For a great tutorial on lots of useful Lisp idoms, see here;
- For a good lille manual see the Simplified Common Lisp reference;
- For notes on why LISP is so awesome, see below.
Give me less than dozen primitives and I can give you a language more powerful that Fortran, Cobol,... many modern languages
- "In 1960, John McCarthy published a remarkable paper in which he did for programming something like what Euclid did for geometry. He showed how, given a handful of simple operators and a notation for functions, you can build a whole programming language. He called this language Lisp, for "List Processing," because one of his key ideas was to use a simple data structure called a list for both code and data."
- "It's worth understanding what McCarthy discovered, not just as a landmark in the history of computers, but as a model for what programming is tending to become in our own time. It seems to me that there have been two really clean, consistent models of programming so far: the C model and the Lisp model. These two seem points of high ground, with swampy lowlands between them. As computers have grown more powerful, the new languages being developed have been moving steadily toward the Lisp model. A popular recipe for new programming languages in the past 20 years has been to take the C model of computing and add to it, piecemeal, parts taken from the Lisp model, like runtime typing and garbage collection."
Let me show you the power of LISP....
- Expressions: atom or (zero or more expresions)
(quote x)
returns x(atom x)
returnst
(true) ifx
is an atom or empty list(eq x y)
returnst
if these are the same atom or both the empty list- Make a list using
(cons x y)
returns a list whose head isx
and whose tail isy
.
- Inspect a list:
(car x)
first item in a list - Inspect a list:
(cdr x)
other items in a list - Dont run all code:
(cond (p1 e1) (p2 e2) (p3 e3))
run the firste
with a truep
.
(Named after the lambda calculus of Alfonso Church.)
LISP functions as a list whose:
- first item is the atom
lambda
, - second item is a list of
parameter
atoms - remaining items is an
expression
.
((lambda (x)(cons x '(b)) 'a) ==> (a b)
((lambda (x y)(cons x (cdr y))) 'z '(a b c))==> (z b c)
Which also means I can pass in a lambda to a lambda
((lambda (f) (f '(b c))) ; <=== lambda body
'(lambda (x) (cons 'a x))) ; <=== 1st arg = lambda
)
===>
(a b c)
Lambdas process variables in a environment
which is, conceptually, a list of cons symbol/values. Note
that this environment also holds lambda bodies (e.g. first
is a synonym for (lambda (x) (car x))
).
'((a 1)
(b 1)
(first (lambda (x) (car x)))
(a 10)
...)
When we call a lambda body:
- the new variables in the parameter list are appended to the left of the envrionment list.
- When we refer to variables inside a lambda body, we look them up from the left
- This means that left-hand-side vars can shadow right-hand-side vars (so when we look up
a
in the above, we get 1, not 10). This will be useful when we pass environments to sub-routines (see below).
Now in case you blinked and missed it, you got a full-fledged programming language.
Consider functions:
(defun hello (who)
(format t "hello ~a.~%" who))
Internally, we can implement these as a lambda body
that is stored in the named memory location hello
.
Consider recursive function calls:
- When we call a function recursively, then the variables for this level are most left and the variables for the calling levels of recursion are on the right
- This means that when we look up a value ina function, we do not mix up the settings from this level with those from the outer levels.
Lambda bodies are just data so I can carry them around a list just like any other variable.
(defun for (start stop f)
(when (< start stop) ;
(funcall f start)
(for (+ 1 start) stop f)))
(If use when
since standard if
assumes 3rd arg is the else
part
while when
just uses all the parts as then
.)
(for 0 5 #'print)
0
1
2
3
4
Not the use of recursion in the above. This gets hard if each recursive call builds up stack entries of function call blocks and local variables (which can be very bad for very deep recursion; e.g. (for 0 10000000 #'print)
. So
our ideal LISP systems comes with tail call optimization.
For even cooler examples of higher order functions, see Norvig's lis.py. This is a (semi-standard-ish) LISP
implementation that is crazy short (100 lines of code) but the STUFF it can do
(note, in his code, (define x y)
says that in the current environment,
x
has the value y
.)
lis.py> (define circle-area (lambda (r) (* pi (* r r))))
lis.py> (circle-area 3)
28.274333877
lis.py> (define fact (lambda (n)
(if (<= n 1)
1
(* n
(fact (- n 1))))))
lis.py> (fact 10)
3628800
lis.py> (fact 100)
9332621544394415268169923885626670049071596826438162146859296389521759999322991
5608941463976156518286253697920827223758251185210916864000000000000000000000000
lis.py> (circle-area (fact 10))
4.1369087198e+13
lis.py> (define first car)
lis.py> (define rest cdr)
lis.py> (define count (lambda (item L)
(if L
(+ (equal? item (first L))
(count item (rest L)))
0)))
lis.py> (count 0 (list 0 1 2 3 0 0))
3
lis.py> (count (quote the) (quote (the more the merrier the bigger the better)))
4
lis.py> (define twice (lambda (x) (* 2 x)))
lis.py> (twice 5)
10
lis.py> (define repeat (lambda (f)
(lambda (x)
(f (f x)))))
lis.py> ((repeat twice) 10)
40
lis.py> ((repeat (repeat twice)) 10)
160
lis.py> ((repeat (repeat (repeat twice))) 10)
2560
lis.py> ((repeat (repeat (repeat (repeat twice)))) 10)
655360
lis.py> (pow 2 16)
65536.0
lis.py> (define fib (lambda (n)
(if (< n 2)
1
(+ (fib (- n 1))
(fib (- n 2))))))
lis.py> (define range (lambda (a b)
(if (= a b)
(quote ())
(cons a
(range (+ a 1)
b)))))
lis.py> (range 0 10)
(0 1 2 3 4 5 6 7 8 9)
lis.py> (map fib (range 0 10))
(1 1 2 3 5 8 13 21 34 55)
lis.py> (map fib (range 0 20))
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)
For yet another example,
the let
form of LISP defines some local variables; e.g.
(let ((x 1)
(y 2))
(let ((x 3))
(print (+ x y))) ==> prints + 3 2 ==> 5
(print (+ x y))) ==> print + 1 2 ==> 3
Internally, we can implement these lets at lambda bodies such that the method look up inside the let bodies does the right things. For example:
(let ((var1 value1)
(var2 value2)
...)
body)
becomes
((lambda (var1 var2 ...) body) value1 value2 ...)
For yet another another example, consider object-oriented
programming. We can define objects as lists of lambda bodies
(in the following, the case
function of new-account
returns one of the lambda bodies).
; Example from Norvig
; https://github.com/norvig/paip-lisp/blob/master/lisp/clos.lisp
(defun new-account (name &optional (balance 0.00)
(interest-rate .06))
"Create a new account that knows the following messages:"
(lambda (message)
(case message
(withdraw (lambda (amt)
(if (<= amt balance)
(decf balance amt)
'insufficient-funds)))
(deposit (lambda (amt) (incf balance amt)))
(balance (lambda () balance))
(name (lambda () name))
(interest (lambda ()
(incf balance
(* interest-rate balance)))))))
In the following
(funcall f m)
==> f(m) in e.g. Python. We use this to get the lambda body.(apply g (x y ...))
==> g(x, y, ...) in e.g. Python. We use this to send the args to the lambda body.
(defun send (object message &rest args)
(apply (funcall object message) args))
This lets us defined some convenience functions:
(defun withdraw (object &rest args)
(apply (send object 'withdraw) args))
For yet another another example, consider the visitor
design pattern
which, in OO languages, needs all these new classes. Not so in LISP
In the following, stufff
is a subclass of magic
(and we'll get to that in the next example).
(defmethod visit ((x t) f)
(funcall f x ))
(defmethod visit ((x cons) f)
(if (funp x)
(funcall f (format nil "(~a)" (second x)))
(mapcar (lambda(y) (visit y f)) x)))
(defmethod visit ((x magic) f)
(mapcar (lambda(slot)
(visit (slot-value x slot) f))
(slots x)))
(visit `(1 2 #'_has "apples" ,(stuff :b 20)) #'print)
This outputs:
1
2
"(_HAS)"
"apples"
1
20
The above uses two magic functions which I include for completeness but need not bother us too much:
(defun funp(x)
(and (consp x)
(eql 2 (length x))
(eql 'function (car x))))
(defmethod slots ((x magic) &optional reveal-all)
(labels
((hide (y)
(and (not reveal-all)
(and (symbolp y)
(equal (elt (symbol-name y) 0) #\_))))
(slots1 (y) ; what are the slots of a class?
#+clisp (class-slots (class-of y))
#+sbcl (sb-mop:class-slots (class-of y)))
(name (y) ; what is a slot's name?
#+clisp (slot-definition-name y)
#+sbcl (sb-mop:slot-definition-name y)))
(remove-if #'hide
(mapcar #'name
(slots1 x)))))
Now we add a macro facility such that high-level constructs unwind into lower-level constructs at load time (so unlike higher-order functions, no runtime overhead).
For example,
LISP has no until
statement. But that can easily be fixed: just add a macro that
defined until
in terms of while
.
(defmacro until (test &body body)
"implements 'until' (which is not standard in LISP)"
`(while (not ,test)
,@body))
Note the backtick notation. This creates a "toggle environment" where "x" evalautes to to the symbol "x" but ",x" evaluates to whatever value "x" points to.
Note also the @body
command. This flattens out a list. So
in the following,
(let ((x 1000))
(until (< x 0)
(setf x (- 1 x))
(print x)))
then (setf x (- 1 x)) (print x)
is @body
and this gets laid
out as flat items as follows:
(let ((x 1000))
(while (not (< x 0))
(setf x (- 1 x))
(print x)))
Whoops, I forgot. LISP has no while
command. Again, easily fixed,
I'll defined while
in terms of the list do
loop command:
(defmacro while (test &body body)
"implements 'while' (which is not standard in LISP)"
`(do ()
((not ,test))
,@body))
ALso suppose I have a table of data
from which I wan the independent numeric
variables from the header. That can be done as follows:
(SLOT-VALUE
(SLOT-VALUE
(SLOT-VALUE DATA 'HEADER)
'NUMERICS)
'INDENDENT)
Which is kinda verbose. The same code can be auto-generated using:
(defmacro ? (obj first-slot &rest more-slots)
"From https://goo.gl/dqnmvH:"
(if (null more-slots)
`(slot-value ,obj ',first-slot)
`(? (slot-value ,obj ',first-slot) ,@more-slots)))
So now
(? data header numerics independent)
automatically expands into the above.
(BTW the above recusrive macro definition took me days to write, and I used a lot of help from my friends.)
For a more interesting example, the following code is my protest against the Common Lisp Object System (which is so verbose). I prefer
(isa stuff ;class
magic ;superclass
(a 1 ) ;slots with defaults
(b 2)
(_cache "tim")))
The equivalent in CLOS is the following, which I'm not going to even to explain to you cause I hate it so much.
(defclass stuff (magic)
((a :initarg :a :initform 1 :accessor stuff-a)
(b :initarg :b :initform 2 :accessor stuff-b)
(_cache :initarg :_cache :initform "tim" :accessor
stuff-_cache)))
I could make the specification of my classes easier with a
macro
that turns (e.g.) (a 1)
into
(a :initarg :a :initform 1 :accessor stuff-a)
Easily done:
(defmacro uses (slot x form)
`(,slot
:initarg ,(intern (symbol-name slot) "KEYWORD")
:initform ,form
:accessor ,(intern (format nil "~a-~a" x slot))))
Here's the whole thing where the uses
macro is now a sub-routine inisde
isa
(why? cause I don't like polluting the global name space).
(defmacro isa (x parent &rest slots)
"simpler creator for claseses. see example in thingeg.lisp"
(labels ((uses
(slot x form)
`(,slot
:initarg ,(intern (symbol-name slot) "KEYWORD")
:initform ,form
:accessor ,(intern (format nil "~a-~a" x slot)))))
`(progn
(defun ,x (&rest inits)
(apply #'make-instance (cons ',x inits)))
(defclass ,x (,parent)
,(loop for (slot form) in slots collect (uses slot x form))))))
(macroexpand '(isa stuff magic (a 1 ) (b 2) (_cache "tim")))
(PROGN
(DEFUN STUFF (&REST INITS)
(APPLY #'MAKE-INSTANCE (CONS 'STUFF INITS)))
(DEFCLASS STUFF (MAGIC)
((A :INITARG :A :INITFORM 1 :ACCESSOR STUFF-A)
(B :INITARG :B :INITFORM 2 :ACCESSOR STUFF-B)
(_CACHE :INITARG :_CACHE :INITFORM "tim" :ACCESSOR
STUFF-_CACHE)))) ;
https://www.youtube.com/watch?v=HM1Zb3xmvMc
The core simplicity of LISP is easier to see it in Norvig's work:
- http://norvig.com/lispy.html
- LISP in Python
- Source code: https://github.com/norvig/pytudes/blob/master/py/lis.py
For learning LISP and having a lot of fun doing it, I strongly endorse the amazing Land of Lisp.
- To edit LISP code, I used to tell folks, EMACS!
- Now I find VIM is just find find (see the excellent slime development environment).
Then, once you get serious, turn to
- Practical Common Lisp.
- The QuickLisp package manager
See also Lua, which is a much simpler language, and was heavily inspired by a LISP variant called SCHEME (and sometimes I think of LUA as "SCHEME without brackets"). LUA is my current "best" language (at least for today...).
Java is everywhere, which means that Java Virtual Machines are everywhere. Creating a version of Lisp that runs on the JVM made it possible to run Lisp anywhere. That was the primary motivation for creating Clojure, and a great reason to learn the language.
Another benefit is that Clojure provides access, via the JVM, to countless tools and third-party libraries written in Java. This gives Clojure a development ecosystem that is more powerful than those previously available to any Lisp dialect.
(See also ClojureScript that transpiles to JavaScript and plugs into all the JS echo system. Has a great REPL in Atom as well.)
Btw, Clojure tells us what LISP overlooked. It is recommended
to start Clojure projects using the lien
package manager. If you do that,
the first thing that happens is that lien
builds you a directory
structure and populates it with some manifest files:
$ lein new app my-stuff
Generating a project called my-stuff based on the 'app' template.
$ cd my-stuff
$ find .
.
./.gitignore
./doc
./doc/intro.md
./LICENSE
./project.clj
./README.md
./resources
./src
./src/my_stuff
./src/my_stuff/core.clj
./test
./test/my_stuff
./test/my_stuff/core_test.clj
That is, the first thing you do in Clojure is to make your code sharable with the international programming community. And that's the final primitive needed to complete LISP- people to code with.