Universitatea Politehnica Bucuresti
2008-2009
Adina Magda Florea http://aimas.cs.pub.ro/fp_09
S-expressions
Data types
Defining variables and procedures
Special forms
Local variables
Control constructs
Objects and pointers
In Scheme, there's no distinction between expressions and statements
They're all "expressions" – s-expressions
Scheme expressions combine the features of expressions and statements.
S-expressions return values
However, they can also have side effects
Simple types : booleans, numbers, characters, and symbols
Booleans
#t for true and #f or () for false
boolean?
checks if its argument is boolean
Numbers integers (eg, 42), rationals (22/7), reals (3.1416), or complex (2+3i)
Predicates: number? complex? real? rational? integer?
eqv?
= , <, <=, >, >= self-evaluating
Characters
Character data - represented by prefixing the character with #\ char?
(char? #\c) => #t
(char? 1) => #f
(char? #\;) => #t self-evaluating
char=?, char<?, char<=?, char>?, char>=?
(char=? #\a #\a) => #t
(char<? #\a #\b) => #t
(char>=? #\a #\b) => #f
To make the comparisons case-insensitive, use char-ci (=?...)
Symbols
(symbol? ' xyz) => #t
(symbol? 42) => #f
( eqv? ' Calorie ' calorie) => #t
( define xyz 9) xyz => 9
- are identifiers
- are evaluated
(unless quote is used) mutation procedure
We can use the form set!
(pronounced "set-bang") to change the value held by a variable:
( set!
xyz #\c)
Note on type xyz => #\c
You should not use assignments a lot in Scheme programs.
It's usually a sign of bad style
Strings
"Hello, World!" => "Hello, World!"
(string #\h #\e #\l #\l #\o) => "hello"
( define greeting "Hello; Hello!")
( string-ref greeting 0) => #\H
New strings can be created by appending other strings:
( string-append "E " "Pluribus " "Unum") => "E
Pluribus Unum"
Make a string of a specified length, and fill it with the desired characters later.
( define a-3-char-long-string (make-string 3))
string?
Strings obtained as a result of calls to string , make-string , and string-append are mutable.
( define hello (string #\H #\e #\l #\l #\o)) hello => "Hello"
( string-set!
hello 1 #\a) hello => "Hallo"
Vectors
( vector 0 1 2 3 4) => #(0 1 2 3 4)
( define v (make-vector 5))
( set!
v #(1 2 3 4 6)) v => #5(1 2 3 4 6)
( vector?
#(1 2 3)) => #t
( vector-ref #(1 2 3) 0) => 1
( vector-ref #(1 2 3) 3) => vector-ref: index 3 out of range [0, 2] for vector: #3(1 2 3)
( vector-set!
v 1 10) v => #5(1 10 3 4 6)
A dotted pair is a compound value made by combining any two arbitrary values into an ordered couple.
(cons 1 #t) => (1 . #t)
( define x (cons 1 #t))
(car x) => 1
(cdr x) => #t
(set-car! x 2)
(set-cdr! x #f) x => (2 . #f)
(car (car y)) => 1
(cdr (car y)) => 2
(caar y) => 1
(cdar y) => 2
( define y (cons (cons 1 2) 3)) y => ((1 . 2) . 3)
The abbreviation for a dotted pair of the form
(1 . (2 . (3 . (4 . ())))) is (1 2 3 4)
This special kind of nested dotted pair is called a list
(cons 1 (cons 2 (cons 3 (cons 4 ' ()))))
(list 1 2 3 4) => (1 2 3 4)
(define y (list 1 2 3 4))
(list-ref y 0) => 1
(list-ref y 3) => 4
(list-tail y 1) => (2 3 4)
(list-tail y 3) => (4)
The predicates pair?, list?, and null? check if their argument is a dotted pair, list, or the empty list,
(pair? ' (1 . 2)) => #t
(pair? ' (1 2)) => #t
(pair? ' ()) => #f
(list? ' ()) => #t
(null? ' ()) => #t
(list? ' (1 2)) => #t
(list? ' (1 . 2)) => #f
(null? ' (1 2)) => #f
(null? ' (1 . 2)) => #f
List of pairs:
car field hold the pointers to the object
cdr field link the pairs together into a "spine."
A list is really just a sequence of pairs, ending with a null pointer.
A null pointer is a list, too - it's a sequence of zero pairs ending in a null pointer.
An object can easily be in many lists at once, because a list is really just a spine of pairs that holds pointers to the items in the list.
foo bar
22 pair pair
13 pair pair
15 pair
quote takes exactly one argument, and returns a data structure whose printed representation is the same as what you typed in as the argument to quote.
Scheme does not evaluate the argument to quote as an expression - it just gives you a pointer to a data structure.
For example, the expression
(define foo (quote (1 2 3))) defines (and binds) a variable foo, and initializes its binding with (a pointer to) a three-element list.
(define (foo) '(1 2 3))
The list (1 2 3) may be created when we define the procedure foo, and each time we call it, it may return a pointer to that same list.
For this reason, it's an error to modify a data structure returned from a quote form.
If we want the procedure foo to return a new list (1 2 3) every time, we can write
(define (foo) (list 1 2 3))
length
(define ( my-length lis)
(cond ((null? lis) 0)
(else (+ 1 ( my-length (cdr lis)))))) append
( define ( my-append lis1 lis2)
(cond ((null? lis1) lis2)
(else (cons (car lis1) ( my-append (cdr lis1) lis2)))))
(my-append '(a b c) '(x y)) => (a b c x y)
(my-append '((a b) c (d e)) '(x (y z) t)) => ((a b) c (d e) x (y z) t)
append concatenates the lists it is given.
It only concatenates the top-level structure, however - it doesn't "flatten" nested structures.
append doesn't modify any of its arguments, but the result of append generally shares structure with the last list it's given.
It effectively conses the elements of the other lists onto the last list to create the result list.
It's therefore dangerous to make a "new" list with append and then modify the "old" list.
This is one of the reasons side effects are discouraged in
Scheme.
Copying lists
There are two common senses of copying, shallow copying, and deep copying.
Shallow copy
( define ( pair-copy pr)
(cons (car pr) (cdr pr)))
Copying lists
Deep copy
( define ( pair-tree-deep-copy thing)
(if (not (pair? thing)) thing
(cons ( pair-tree-deep-copy (car thing))
( pair-tree-deep-copy (cdr thing)))))
Copying lists
( define ( list-copy lis)
(cond ((null? lis) '())
(else (cons (car lis) ( list-copy (cdr lis))))
Primitives and procedures
The variable denoting a Scheme primitive or a defined procedure holds that procedure cons => #<primitive:cons> car => #<primitive:car> list-sum => #<procedure:list-sum>
When you write a procedure that modifies its arguments, rather than just returning a value, it's good style to give it a name that ends with !
e.g. reverse vs reverse!
( define ( my-reverse lis)
(cond ((null? lis) ())
(else
(append ( my-reverse (cdr lis))
(list (car lis))))))
(define m '(a b c))
( my-reverse m) => (c b a) m => (a b c)
; returns the last element of a list
; to be used in my-reverse!
( define ( all-but-last lis)
(cond ((null? lis) ())
((null? (cdr lis)) ())
(else (cons (car lis) ( all-but-last (cdr lis))))))
;my-reverse! distroys the initial list by reversing it
( define ( my-reverse!
lis)
(let ((l ()) (len (length lis)))
(cond ((null? lis) ())
(else (set! l (list-ref lis (- len 1)))
(set-cdr! lis ( my-reverse!
(all-but-last lis)))
(set-car! lis l) lis))))
( all-but-last '(a b c)) => (a b)
(define m '(a b c))
( my-reverse!
m) => (c b a) m => (c b a)
; my-rev! seems to destroy the initial list but in fact it does not
( define ( my-rev!
lis)
(cond ((null? lis) ())
(else (set! lis (append ( my-rev!
(cdr lis))
(list (car lis)))) lis)))
(define m '(a b c))
( my-rev!
m) => (c b a) m => (a b c)
Yet another data type is the port .
A port is the conduit through which input and output is performed.
Ports are usually associated with files and consoles.
Defining a variable :
( define my-variable 5) - tells Scheme to allocate space for my-variable , and initialize that storage with the value 5.
In Scheme, you always give a variable an initial value, so there's no such thing as an uninitialized variable or an unininitialized variable error.
Scheme values are always pointers to objects
e.g., when we use the literal 5, Scheme interprets that as meaning a pointer to the object 5.
Numbers are objects you can have pointers to, just like any other kind of data structure.
The define expression does three things:
It declares to Scheme that we're going to have a variable named foo in the current scope.
It tells Scheme to actually allocate storage for the variable. The storage is called a binding .
It tells Scheme what initial value to put in the storage.
You can not use set!
on a variable that has not been defined
Defining a procedure:
Use the special form lambda to defined an un-named procedure
( lambda (x) (+ x 2))
(( lambda (x) (+ x 2)) 5) => 7
Named procedures: use a variable to hold the procedure value:
( define add2 ( lambda (x) (+ x 2)))
(add2 4) => 6
(add2 9) => 11
Defining a procedure:
Or just use define and indicate name and parameters
( define (two-times x) (+ x x))
When you define a procedure, you're really defining a variable whose value happens to be a (pointer to a) procedure .
You can define a procedure with 0 parameters
( define (foo) 15)
Mind the difference ( define foo 15)
Variable number of arguments:
Some procedures can be called at different times with different numbers of arguments.
To do this, the lambda parameter list is replaced by a single symbol.
This symbol acts as a variable that is bound to the list of the arguments that the procedure is called on.
The lambda parameter list can be:
a list of the form (x ...) a symbol a dotted pair of the form (x ... . z); in the dotted-pair case, all the variables before the dot are bound to the corresponding arguments in the procedure call, with the single variable after the dot picking up all the remaining arguments as one list.
Special forms:
A kind of procedure but behaves differently
Procedure calls and special forms are syntactically similar but semantically different
Examples:
set!
- isn't a procedure, because its first argument is not really an expression to be evaluated in the normal way, to get a value to pass as an argument. It's the name of a place to put a value
define treats its first argument specially--the name of a variable or procedure isn't an expression that is evaluated and passed to define - it's just a name, and you're telling define to allocate some storage and use that name for it.
Other special forms we'll see include
control constructs : if, cond, and case, etc., and logical operators and and or;
forms for defining local variables : let and its variants letrec and let*;
looping constructs : named let and do;
quote , which let you write complex data structures as textual literals in your code, and
lambda , which creates new procedures
forms for defining local variables : let and its variants letrec and let*;
control constructs : if, cond, and case, etc., and logical operators and and or;
Conceptually, all Scheme objects are allocated on the heap, and referred to via pointers.
A procedure takes pointers to the arguments and returns a pointer to the value computer and returned by the procedure
This makes things simpler
• An implementation is free to optimize away the pointers if it doesn't affect the programmer's view of things
• Most implementations actually do this