Introduction to Lisp

Download Report

Transcript Introduction to Lisp

Introduction to Lisp
For Scheme Users
What Makes Lisp Different?
• Built-in
Support for Lists
• Automatic Storage Management
• Dynamic Typing
• First-Class Functions
• Uniform Syntax
• Interactive Environment
• Extensibility
• History
Lisp vs. Scheme
1.
2.
3.
4.
5.
Lisp has much more built-in functions and special
forms, the Scheme language definition takes 45 pages
while Common Lisp takes 1029 pages)
Apart from lexical variables Lisp also has special
variables
Scheme uses one name space for functions, variables,
etc., Lisp doesn’t.
Scheme evaluates the function part of a function call in
exactly the same way as arguments, Lisp doesn’t.
Lisp functions can have rest, optional and keyword
parameters. Scheme functions only can have the
equivalent of a rest parameter.
Lisp vs. Scheme (2)
6.
7.
8.
9.
Lisp has block, return, go or throw, in Scheme a single
function (call-with-current-continuation or call/cc)
replaces all these and does much more.
Lisp has built-in packages while package-like
structures can be implemented in Scheme using
lexical variables.
Lisp has standard macros, Scheme doesn’t although
most implementations provide macros as an
extension. Still true in latest definition of Scheme?
Lisp has special forms (loop, do, dotimes, …) for
looping, in Scheme the user is asked to use tailrecursion that is implemented efficiently.
Scheme vs. Lisp
•
•
•
•
•
•
•
var
constant
(quote x) or ’x
(set! var x)
(if p a b)
(lambda parms x…)
(fn arg …)
• #t, #f, ()
• (define var exp)
• (define (fn parm…) body)
•
•
•
•
•
•
•
var
constant
(quote x) or ’x
(setq var x)
(if p a b)
#’(lambda parms x…)
(fn arg …) or (funcall fn
arg …)
• t, nil, nil
• (defparameter var exp)
• (defun fn (parm…) body)
Scheme vs. Lisp (2)
•
•
•
•
•
•
•
•
•
•
•
•
char-ready?
char?
eq?
equal?
eqv?
even?
for-each
integer?
map
negative?
pair?
procedure?
•
•
•
•
•
•
•
•
•
•
•
•
listen
characterp
eq
equal
eql
evenp
mapc
integerp
mapcar
minusp
consp
functionp
Scheme vs. Lisp (3)
•
•
•
•
set!
set-car!
vector-set!
string-set!
•
•
•
•
setq, setf
replaca, setf
setf
setf
Parameters in Scheme
(define (foo-1 arg1 . rest)
body)
(foo-1 1 2 3 4 5)
arg1  1
rest  (2 3 4 5)
(foo-1 1)
arg1  1
rest  ()
(define (bar arg1 arg2)
body)
(bar 1 2)
Parameters in Lisp
(defun foo-1 (arg1 &rest rest)
body)
(defun foo-2 (arg1 &optional
(arg2 default-value))
body)
(foo-1 1 2 3 4 5)
arg1  1
rest  (2 3 4 5)
(foo-1 1 2 3 4 5)
arg1  1
rest  (2 3 4 5)
(foo-2 2)
arg1  2
arg2  default-value
(foo-2 2 3)
arg1  2
arg2  3
Parameters in Lisp
(defun foo-3
(&key (op ‘+)(range 100)
(n 10))
body … )
(foo-3 :n 5)
op  +
range  100
n5
(foo-3 :n 20 :op ’* :range 10)
op  *
range  10
n  20
(foo-3)
op  +
range  100
n  10
Example: Names
;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*;;; Code from Paradigms of Artificial Intelligence Programming
;;; Copyright (c) 1991 Peter Norvig
;;;; File intro.lisp: Miscellaneous functions from the introduction.
(defun last-name (name)
"Select the last name from a name represented as a list."
(first (last name)))
(defun first-name (name)
"Select the first name from a name represented as a list."
(first name))
(setf *names* '((John Q Public) (Malcolm X) (Admiral Grace Murray Hooper)
(Spot) (Aristotle) (A A Milne) (Z Z Top) (Sir Larry Olivier)))
Names (2)
(defparameter *titles*
'(Mr Mrs Miss Ms Sir Madam Dr Admiral Major General)
"A list of titles that can appear at the start of a name.")
(defun first-name (name)
"Select the first name from a name represented as a list."
(if (member (first name) *titles*)
(first-name (rest name))
(first name)))
A Scheme Interpreter
scheme
interp
def-scheme-macro
*scheme-procs*
Top-level Functions
A Scheme read-eval-print loop
Evaluate an expression in an
environment
Define a Scheme macro
Special Variables
Some procedures to store in the
global environment
A Scheme Interpreter (2)
set-var!
get-var
set-global-var!
get-global-var
extend-env
init-scheme-interp
init-scheme-proc
Auxiliary Functions
Set a variable to a value
Get the value of variable in an
environment
Set a global variable to a value
Get the value of a variable from the
global environment
Add some variables and values to an
environment
Initialize some global variables
Define a primitive Scheme procedure
A Scheme Interpreter (3)
Auxiliary Functions (cont.)
scheme-macro
Retrieve the Scheme macro for a
symbol
scheme-macro-expand Macro-expand a Scheme expression
maybe-add
Add an element to the front of a
non-singleton list
print-proc
Print a procedure
Data Type (tail-recursive version
only)
A Scheme procedure
proc
A Scheme Interpreter (4)
interp-begin
interp-call
map-interp
call/cc
last1
length=1
Functions (continuation version only)
Interpret a begin expression
Interpret a function application
Map interp over a list
call with current continuation
Previously Defined Functions
Select the last element of a list
Is this list of length 1?
A Basic Scheme Interpreter
1.
2.
3.
4.
5.
6.
7.
8.
If the expression is a symbol, look up its value in the environment
If it is an atom that is not a symbol (such as a number), just return
it.
Otherwise, the expression must be a list. If the list starts with
quote, return the quoted expression
If it starts with begin, interpret each subexpression, and return the
last one
If it starts with set!, interpret the value and then set the variable to
that value
If it starts with if, then interpret the test, and depending on whether
it is true or not, interpret the then-part or the else-part
If it starts with lambda, build a new procedure – a closure over the
current environment
Otherwise, it must be a procedure application. Interpret the
procedure and all it arguments, and apply the procedure value to
the argument values
(defun interp (exp &optional env)
"Interpret (evaluate) the expression exp in the environment env."
(cond
((symbolp exp) (get-var exp env))
((atom exp) exp)
((case (first exp)
(QUOTE (second exp))
(BEGIN (last1 (mapcar #'(lambda (y) (interp y env))
(rest exp))))
(SET! (set-var! (second exp) (interp (third exp) env) env))
(IF (if (interp (second exp) env)
(interp (third exp) env)
(interp (fourth exp) env)))
(LAMBDA (let ((parms (second exp))
(code (maybe-add 'begin (rest2 exp))))
#'(lambda (&rest args)
(interp code (extend-env parms args env)))))
(t ;; a procedure application
(apply (interp (first exp) env)
(mapcar #'(lambda (v) (interp v env))
(rest exp))))))))
(defun set-var! (var val env)
"Set a variable to a value, in the given or global environment."
(if (assoc var env)
(setf (second (assoc var env)) val)
(set-global-var! var val))
val)
(defun get-var (var env)
"Get the value of a variable, from the given or global environment."
(if (assoc var env)
(second (assoc var env))
(get-global-var var)))
(defun set-global-var! (var val)
(setf (get var 'global-val) val))
(defun get-global-var (var)
(let* ((default "unbound")
(val (get var 'global-val default)))
(if (eq val default)
(error "Unbound scheme variable: ~a" var)
val)))
(defun extend-env (vars vals env)
"Add some variables and values to an environment."
(nconc (mapcar #'list vars vals) env))
(defparameter *scheme-procs*
'(+ - * / = < > <= >= cons car cdr not append list read member
(null? null) (eq? eq) (equal? equal) (eqv? eql)
(write prin1) (display princ) (newline terpri)))
(defun init-scheme-interp ()
"Initialize the scheme interpreter with some global variables."
;; Define Scheme procedures as CL functions:
(mapc #'init-scheme-proc *scheme-procs*)
;; Define the boolean `constants'. Unfortunately, this won't
;; stop someone from saying: (set! t nil)
(set-global-var! t t)
(set-global-var! nil nil))
(defun init-scheme-proc (f)
"Define a Scheme procedure as a corresponding CL function."
(if (listp f)
(set-global-var! (first f) (symbol-function (second f)))
(set-global-var! f (symbol-function f))))
Syntactic Extension of the Basic
Interpreter with Macros
• Once we have a basic Scheme interpreter, the
remaining syntax can be defined as “derived
expressions” in terms of the five primitives:
quote, begin, set! , if and lambda
• The following forms are used (nearly) identically
in Scheme and Lisp: let, let*, and, or, do, cond
and case
Syntactic Extension of the Basic
Interpreter with Macros (2)
The final three syntactic extensions are
unique to Scheme:
(define var val) or (define (proc-name arg …) body
…)
(delay expression)
(letrec ((var init) …) body …)
Syntactic Extension of the Basic
Interpreter with Macros (3)
• Macro: a form that the evaluator first expands
into some other form which is then evaluated.
• Macros allow the user to extend the language,
i.e. we have a programmable programming
language
• First, we have to change interp to allow macros
• Then, we have to provide a mechanism for
defining macros
Macro Evaluation
• Expansion, e.g.
(schema-macro-expand
‘(let ((x 1)
(y 2))
(+ x y)))
((lambda (x y) (+ x y)) 1 2)
• Evaluation
(defun interp (x &optional env)
"Interpret (evaluate) the expression x in the environment env.
This version handles macros."
(cond
((symbolp x) (get-var x env))
((atom x) x)
((scheme-macro (first x))
(interp (scheme-macro-expand x) env))
((case (first x)
(QUOTE (second x))
…
(t
(apply (interp (first x) env)
(mapcar #'(lambda (v) (interp v env))
(rest x))))))))
(defun scheme-macro (symbol)
(and (symbolp symbol) (get symbol 'scheme-macro)))
(defmacro def-scheme-macro (name parmlist &body body)
"Define a Scheme macro."
`(setf (get ',name 'scheme-macro)
#'(lambda ,parmlist .,body)))
(defun scheme-macro-expand (x)
"Macro-expand this Scheme expression."
(if (and (listp x) (scheme-macro (first x)))
(scheme-macro-expand
(apply (scheme-macro (first x)) (rest x)))
x))
(def-scheme-macro let (bindings &rest body)
`((lambda ,(mapcar #'first bindings) . ,body)
.,(mapcar #'second bindings)))
(def-scheme-macro let* (bindings &rest body)
(if (null bindings)
`(begin .,body)
`(let (,(first bindings))
(let* ,(rest bindings) . ,body))))
(def-scheme-macro and (&rest args)
(cond ((null args) 'T)
((length=1 args) (first args))
(t `(if ,(first args)
(and . ,(rest args))))))
(def-scheme-macro or (&rest args)
(cond ((null args) 'nil)
((length=1 args) (first args))
(t (let ((var (gensym)))
`(let ((,var ,(first args)))
(if ,var ,var (or . ,(rest args))))))))
(def-scheme-macro cond (&rest clauses)
(cond ((null clauses) nil)
((length=1 (first clauses))
`(or ,(first clauses) (cond .,(rest clauses))))
((starts-with (first clauses) 'else)
`(begin .,(rest (first clauses))))
(t `(if ,(first (first clauses))
(begin .,(rest (first clauses)))
(cond .,(rest clauses))))))
(def-scheme-macro case (key &rest clauses)
(let ((key-val (gensym "KEY")))
`(let ((,key-val ,key))
(cond ,@(mapcar
#'(lambda (clause)
(if (starts-with clause 'else)
clause
`((member ,key-val ',(first clause))
.,(rest clause))))
clauses)))))
(def-scheme-macro define (name &rest body)
(if (atom name)
`(begin (set! ,name . ,body) ',name)
`(define ,(first name)
(lambda ,(rest name) . ,body))))
(def-scheme-macro delay (computation)
`(lambda () ,computation))
(def-scheme-macro let-rec (bindings &rest body)
`(let ,(mapcar #'(lambda (v) (list (first v) nil)) bindings)
,@(mapcar #'(lambda (v) `(set! .,v)) bindings)
.,body))
A Tail-Recursive Interpreter
• Lisp has many special forms for looping: do, do*,
dolist, loop, …
(defun element-p (element list)
(loop for el in list
when (eq el element) return t))
• Scheme encourages to use tail-recursion
instead
(define (element? element list)
(cond ((null? list) #f)
((eq? element (first list)) #t)
(else (element? element (rest list))))
(defun interp (x &optional env)
"This version is properly tail-recursive."
(prog () :INTERP
(return (cond ((symbolp x) (get-var x env))
((atom x) x)
((scheme-macro (first x))
(setf x (scheme-macro-expand x))(GO :INTERP))
((case (first x)
(QUOTE (second x))
(BEGIN (pop x) ; pop off the BEGIN to get at the args
;; Now interpret all but the last expression
(loop while (rest x) do (interp (pop x) env))
;; Finally, rename the last expression as x
(setf x (first x)) (GO :INTERP))
(SET! (set-var! (second x) (interp (third x) env) env))
(IF
(setf x (if (interp (second x) env)
(third x)
(fourth x))) ; That is, rename the right expression as x
(GO :INTERP))
;; Continued on the next page
((case (first x)
;; Continuation of the previous page
(LAMBDA (make-proc :env env
:parms (second x)
:code (maybe-add 'begin (rest2 x))))
;; A procedure application
(t (let ((proc (interp (first x) env))
(args (mapcar #'(lambda (v) (interp v env))(rest x))))
(if (proc-p proc)
;; Execute procedure with rename+goto
(progn (setf x (proc-code proc))
(setf env (extend-env
(proc-parms proc)
args
(proc-env proc)))
(GO :INTERP))
;; else apply primitive procedure
(apply proc args))))))))))
(defstruct (proc (:print-function print-proc))
"Represent a Scheme procedure"
code (env nil) (name nil) (parms nil))
(setf *proc* (make-PROC :name ’my-proc
:parms ’ (x y)
:code ’ (+ x y))
(defun print-proc (proc &optional (stream *standard-output*) depth)
(declare (ignore depth))
(format stream "{~a}" (or (proc-name proc) '??)))
An Interpreter Supporting Call/cc
• Lisp has different non-local exits, e.g. catch and throw
(defun print-table (l)
(catch ’not-a-number
(mapcar #’print-sqrt-abs l)))
(defun print-sqrt-abs (x)
(print (sqrt (abs (must-be-number x)))))
(defun must-be-number (x)
(if (numberp x) x
(throw ’not-a-number “huh?”)))
 (print-table ’(1 4 -9 x 10 20))
1, 2, 3, “huh?”
An Interpreter Supporting Call/cc
• Scheme on the contrary has only one construct,
call/cc:
(define (print-table l)
(call/cc (lambda (escape) (set! not-a-number escape)
(map print-sqrt-abs l)))
(define (print-sqrt-abs x)
(write (sqrt (abs (must-be-number x)))))
(define (must-be-number x)
(if (numberp x) x
(not-a-number “huh?”)))
 (print-table ’(1 4 -9 x 10 20)) 1, 2, 3, “huh?”
Continuations in Scheme
Consider the Scheme expression
(* (f1 exp1) (f2 (f3 4) (f5 exp2)))
The continuation of (f3 4) in that expression is
the function
(lambda (VoE)
(* (f1 exp1) (f2 VoE (f5 exp2))))
The continuation c of an expression e is a
function that awaits the value of e and proceeds
the computation.
Continuations in Scheme (2)
Again, consider the Scheme expression
(* (f1 exp1) (f2 (f3 4) (f5 exp2)))
The continuation of (f2 (f3 4) (f5 exp2)) in that
expression is the function
(lambda (val)
(* (f1 exp1) val))
Continuations in Scheme (3)
In Scheme, every expression has a continuation
and the evaluator always has a function at
hand representing that continuation.
Furthermore, Scheme has a primitive function to
get access to this continuation, i.e.
call-with-current-continuation or call/cc.
call/cc requires a function of one argument, and
it immediately call this function thereby passing
it the continuation of the entire call/cc
expression.
A Continuation Example
(define aFuture '())
(display (+ 2 (call/cc (lambda (cont)
(set! aFuture cont) 8))))
Another Continuation Example:
Error handling
(define error '())
(call/cc (lambda (cont) (set! error cont) (p)))
A Last Continuation Example:
Chronological Backtracking
(define (integer) (amb 1 (+ 1 (integer))))
(define (prime)
(let ((n (integer)))
(if (prime? n) n (fail))
(def-scheme-macro amb (x y)
‘(random-choice (lambda () ,x) (lambda () ,y)))
A Last Continuation Example:
Chronological Backtracking (2)
(define backtrack-points '())
(define (fail)
(let ((last-choice (first backtrack-points)))
(set! backtrack-points (rest backtrack-points))
(last-choice)))
(define (random-choice f g)
(if (=1 (random 2))(choose-first f g)
(choose-first g f)))
(define (choose-first f g)
(call/cc (lambda (k)
(set! backtrack-points
(cons (lambda () (k (g))) backtrack-points))
(f))))
(defun interp (x env cc)
"Evaluate the expression x in the environment env,
and pass the result to the continuation cc."
(cond ((symbolp x) (funcall cc (get-var x env)))
((atom x) (funcall cc x))
((scheme-macro (first x))(interp (scheme-macro-expand x) env cc))
((case (first x)
(QUOTE (funcall cc (second x)))
(BEGIN (interp-begin (rest x) env cc))
(SET! (interp (third x) env
#'(lambda (val)
(funcall cc (set-var! (second x) val env)))))
(IF (interp (second x) env
#'(lambda (pred)
(interp (if pred (third x) (fourth x)) env cc))))
(LAMBDA (let ((parms (second x))
(code (maybe-add 'begin (rest2 x))))
(funcall cc
#'(lambda (cont &rest args)
(interp code (extend-env parms args env) cont)))))
(t (interp-call x env cc))))))
(defun scheme (&optional x)
"A Scheme read-eval-print loop (using interp).
Handles call/cc by explicitly passing continuations."
(init-scheme-interp)
(if x (interp x nil #'print)
(loop (format t "~&==> ")
(interp (read) nil #'print))))
(defun interp-begin (body env cc)
"Interpret each element of BODY, passing the last to CC."
(interp (first body) env
#'(lambda (val)
(if (null (rest body)) (funcall cc val)
(interp-begin (rest body) env cc)))))
(defun interp-call (call env cc)
"Interpret the call (f x...) and pass the result to CC."
(map-interp call env
#'(lambda (fn-and-args)
(apply (first fn-and-args) cc (rest fn-and-args)))))
(defun map-interp (list env cc)
"Interpret each element of LIST, and pass the list to CC."
(if (null list) (funcall cc nil)
(interp (first list) env
#'(lambda (x)
(map-interp (rest list) env #'(lambda (y)
(funcall cc (cons x y))))))))
(defun init-scheme-proc (f)
"Define a Scheme primitive procedure as a CL function."
(if (listp f)
(set-global-var! (first f)
#'(lambda (cont &rest args)
(funcall cont (apply (second f) args))))
(init-scheme-proc (list f f))))
(defun call/cc (cc computation)
"Make the continuation accessible to a Scheme procedure."
(funcall computation cc
;; Package up CC into a Scheme function:
#'(lambda (cont val)
(declare (ignore cont))
(funcall cc val))))
;; Now install call/cc in the global environment
(set-global-var! 'call/cc #'call/cc)
(set-global-var! 'call-with-current-continuation #'call/cc)