Project 1
Group Members:
VALLURI Vamsidhar, WONG Mei Fun , YEE Meng Yi and ZHANG JingYuan
Scheme
History | Overview of Language | Handling of Data Objects | Handling of Sequence Control | Handling of Subprograms and Storage Management | Handling of Abstraction and Encapsulation | Comparison and Contrast with Lisp | Reference |
Scheme was introduced in 1975 by Gerald J. Sussman and Guy L. Steele Jr. as the first dialect of Lisp to fully support lexical scoping, first-class procedures, and continuations. At first, it was a very small language which was used for research and teaching, support only a handful of predefined syntactic forms and procedures. Now, Scheme is a complete general-purpose programming language. The early implementations of Scheme were interpreter-based and slow. Some modern Scheme implementations boast sophisticated compilers that generate code on par with code generated by the best optimizing compilers for lower-level languages such as C and Fortran.
Scheme is a general-purpose computer programming language. Scheme is also a high-level language, supporting operation on structured data such as strings, lists, vectors, numbers and characters. Scheme is often identified with symbolic applications, its rich set of data types and flexible control structures make it a truly versatile language. Scheme is a fairly simple language to learn. It is based on a handful of syntactic forms and semantic concepts.
Scheme programs are highly portable across implementations of the same Scheme system on different machines. This is because machine dependencies are almost completely hidden from the programmer. Also, because of two related Scheme standardization efforts.
In Scheme, a procedure definition may appear within another block or procedure, and procedure may be invoked at any time thereafter, even if the enclosing block has completed its execution. To support lexical scoping, a procedure carries the lexical context along with its code.
A special case of recursion of Scheme is called tail recursion. It is used to express looping. A tail recursion occurs when a procedure recursively tail calls itself, directly or indirectly. Scheme implementations are required to implement tail calls as jumps (gotos).
Scheme supports the definition of arbitrary control structures with continuations. A continuation is a procedure that embodies the remainder of a program at a given point in the program. When a continuation is invoked, the program immediately continues from that point. It may be obtained at any time during the execution of a program. Continuations allow the implementation of complex control mechanisms including explicit backtracking, multithreading and coroutines.
Scheme handles data values quite differently from most languages. Objects are dynamically allocated in a heap where they are retained until no longer needed, then automatically deallocated. Objects in Scheme are first-class data values because they are heap allocated and retained indefintely, they may be passed freely to procedures, returned as values from procedures and combined to form new objects.
Scheme support many types of objects, including numbers, characters, strings, symbols, and lists or vectors of object. Scheme supports a full set of numeric data types including complex, real and arbitrary-precision rational numbers. This allows Schmes tosupport many numerical applications coded in lower level languages. The following illustrate the data objects in Scheme.
Lists and Pairs
The pair or cons cell is the most fundamental of Scheme's structured object types. The most common use for pairs is to build lists. Pairs may be used to construct binary trees. Each pair in the tree structure is an internal node of the binary tree. Proper lists are printed as sequences of objects separated by whitespace and enclosed in parentheses. Brackets ([ ]) may also be used in some Scheme systems.
Numbers
In Scheme, numbers can be integers, rational numbers, real numbers, or complex numbers. This classification is hierarchical, in that all integers are rational, all rational numbers are real, and all real numbers are complex.
Characters
Characters are atomic objects representing letters, digits, special symbols, and certain nongraphic control characters such as space and newline. Characters are written with a #\ prefix, #\A. The characters newline and spcae may be written in this manner as well, but they can also be written as #\newline or #\space.
Strings
Strings are sequences of characters and are typically used as messages or character buffers. Scheme provides operations for creating strings, extracting characters from strings, obtaining substrings, concatenating strings, and altering the contents of strings.
A string is written as a sequence of characters enclosed in double quotes, "hi". A double quote may be introduced into a string by preceding it by a backward slash, "two \"quotes\" within". A backward slash may also be included by preceding it with a backward slash , " \\ slash". Strings are indexed by exact nonnegative integers, and the index of the first element of any string is 0.
Vectors
Vectors are more convenient and efficient than lists for some applications. Whereas accessing an arbitrary element in a list requires a linear traversal of the list up to the selected element, arbitrary vector elements are accessed in constant time. The length of a vector in Scheme is the number of elements it contains. Vectors are indexed by exact nonegative integers, and the index of the first element of any vector is 0.
A vector is written as a sequence of objects separated by whitespace, preceded by the prefix #( and followed by ), #(a b c).
Symbols
Symbols are used for a variety of purposes as symbolic names in Scheme programs. Strings could be used for most of the same purposes, but an important characteristic of symbols makes comparisons between symbols much more efficient. The property that two symbols may be compared quickly for equivalence makes them ideally suited for use as identifiers in the representation of programs, allowing fast comparison of identifiers. This property also makes symbols useful for a variety of other purposes.
Symbols are written without double quotes or other bracketing characters, parentheses, double quotes, spaces, and most other characters with a special meaning to the Scheme reader are not allowed within the printed representation of a symbol.
Procedure Application
Procedure Application is the most basic Scheme sequence control structure. The syntax is as follow:
( procedure expression )
Any structure without a syntax keyword in Scheme in the first position is a procedure application. The order of the procedure and argument expressions are evaluated unspecified. It may be left to right, right to left or some arbitrary order ( as specified by the programmer). However, the evaluation is guaranteed to be sequential. Each expression is evaluated before the next evaluation is started.
The example below is to illustrate procedure application.
( + 3 4) => 7
Sequencing
To sequence assignments, input/output and other operations,
Scheme uses the begin statement.
Syntax: ( begin expression1 expression2,
)
The expression1 expression2
are evaluated in sequence from
left to right.
Conditionals
Scheme has conditionals of if statements, not statements, and statements, or statements, cond statements and case statements.
Conditionals for Scheme are of the form:
If statement
Syntax: ( if test consequent alternative)
Syntax: ( if test consequent)
Test, consequent and alternative are expressions. The return
value for consequent and alternative will depend on the test
value.
Example:
( if (> n 0) 9 = n 10 ) )
Not Statement
Syntax: ( not object )
If object is true, the Scheme interpreter will return #t for true,
otherwise #f for false.
And Statement
Syntax: ( and expression )
The Scheme interpreter evaluates and subexpressions from left to right and stops immediately ( without evaluating the remaining expressions) if any expression evaluates to false. The last value of the last expression evaluated is returned.
Example:
( let ((x 3))
( and (> x 2) ( < x 4))) => #t
Or statement
Syntax: ( or expression)
The Scheme interpreter too evaluates or subexpressions
from left to right and stops immediately ( without evaluating the
remaining expressions) if any expression evaluates to true.
Example:
( let ((x 3))
( or (< x 2) ( > x 4))) => #f
Cond statement
Syntax: ( cond expression1 expression2
)
Syntax: ( cond text expression1
else
. )
The expressions are evaluated in sequence and return a true a
value, the value of the of the last expression is returned.
Example:
( cond
(( = n 10) (= m 1))
(( > n 10) (= m 2) (= n (* n m)))
((< n 10) (=n 0)))
Case statement
Syntax: ( case expression1 statement1 statement2 )
The case statement evaluates the expression(s) given and return
the value of the corresponding statement.
Example:
(let ((x 4) (y 5))
( case (+ x y)
(( 1 3 5 7 9 ) odd)
((0 2 4 6 8) even)
(else out-of-range))) => odd
Recursion and Iteration
Syntax: ( let name ( var val) .. ) expression1 expression2 )
This form let, called a named let, is a general purpose iteration and recursion construct. This statement returns the value of the final expression.
This statement can also be written as
Syntax: ((letrec (( name lambda (var ) expression1 expression2 ))) name) val )
This statement too returns the value of the final expression. The difference between let and letrec is that letrec allows the definition of mutually recursive procedures.
Syntax: ( do ( var val update) ) ( test res ) expression)
On each step, the test expression test is evaluated. If the value of test is true, iteration ceases, the result expressions res are evaluated in sequence and the value of the last expression is returned.
If the value of test is false, the expression are evaluated in sequence, the expressions update are evaluated, new bindings for var to the values of update are created and iteration continues.
Syntax: ( for-each procedure list1 list2 )
The for-each statement guarantees to perform applications in sequence over the lists from left to right.
Handling of subprograms and storage management
In this discussion, the terms procedure, function, and subprogram will be used interchangeably.
Handling of Subprograms
One of the advantages of using Scheme is that the number of subprograms provided by the language is relatively small, so the programmer does not have to learn to use many subprograms in order to write Scheme programs. Instead, Scheme makes it easy for the programmers to define their own subprograms, as they need them. The lambda syntactic form is used to create subprograms. Any operation that creates a subprogram or establishes local variable bindings is defined in terms of lambda.
Scheme programs are block-structured. Identifiers in Scheme may be bound either at top level or locally. Block structure and lexical scoping help create programs that are modular, easy to read, easy to maintain, and reliable. In Scheme, a subprogram definition may appear within another block or subprogram, and the subprogram may be called at any time after that, even if the enclosing block has completed its execution. To support lexical scoping, a subprogram carries the lexical context or environment along with its code. Furthermore, Scheme subprograms are not always named. Instead, subprograms are first-class data objects like strings or numbers, and variables are bound to subprograms in the same way they are bound to other objects.
Like procedures in many other languages, Scheme procedures may be recursive. There is a special case of recursion in Scheme called tail recursion. It is used to express iteration or looping. A tail call occurs when one subprogram directly returns the result of calling another subprogram. Tail recursion occurs when a subprogram recursively tail calls itself. Scheme implementations are required to implement tail calls as jumps or gotos. As a result, Scheme programmers only need to be familiar with simple procedure calls and recursion and don't need to worry about the usual varieties of loop structures.
Scheme supports the definition of arbitrary control structures with continuations. A continuation is a procedure that embodies the remainder of a program at a given point in the program. When a continuation is called, the program immediately continues from that point. A continuation may be obtained at any time during the execution of a program. As with other subprograms, a continuation is a first-class object and may be called at any time after its creation.
Storage Management
Scheme does not have explicit storage management. Objects, are dynamically allocated in a heap where they are kept until no longer needed, then automatically deallocated. A Scheme program never explicitly deallocates scheme objects such as pairs, strings, and procedures. Instead, the storage management system employs garbage collection which automatically reclaims the storage space. The procedure collect in the Scheme language is used for garbage collection. Once the garbage collector proves that the object is no longer referenced, it deallocates the object and the storage used by the object can be reused later. In order to reclaim storage and prevent the system storage from being all used up, garbage collector runs periodically as a program runs. Although garbage collection is performed automatically, Scheme also allows forced garbage collection at a particular time.
Weak pairs and guardians in Scheme also help with storage management. Weak pairs allow programs to maintain weak pointers to objects. A weak pointer to an object does not prevent the object from being reclaimed by the storage management system, but it does remain valid as long as the object is accessible in the system. Guardians allow programs to protect objects from deallocation by the garbage collector and determines when the objects would otherwise have been deallocated. Weak pairs and guardians allow programs to keep information about objects in separate data structures without having to concern about the possibility that maintaining this information will cause the objects to remain indefinitely in the system. Also, guardians allow objects to be saved from deallocation indefinitely so that they can be reused, cleaned-up, or have other actions performed on the data stored within those objects.
Handling of abstraction and encapsulation
Functional languages are heavily abstracted. A functional program can be viewed as encapsulated inter-operable entities operating at a very high level. . A good portion of the executable code of any functional program is stored in exte
Many functions are currently available as encapsulations of standard functional language libraries. The actual operation of these functions is kept abstracted. The potential complexity of functional programs, as functions are passed a
Scheme Built-in Procedures
Scheme environment, starts out with a number of variables bound to locations containing useful values, most of which are primitive procedures that manipulate data. For example, the variable * is bound to (a location initially containing
When a compound expression (E.g. Arithmetic expression like (+ 10 (* 2 5))) is typed (the one surrounded by parentheses) at the Scheme command interpreter, the interpreter treats it as a procedure call, The first expression are the
Nested Operations
It is easy to evaluate expressions such as given in the example above. The Primitive Procedure Rule is more powerful, thus, we can use those expressions as parts (or subexpressions) of larger expressions. For instance, consider the
(+ 10 (* 2 5)) =20
This procedure calls for the procedure +, so we need to use The Primitive Rule to evaluate it. The first operand, 10, simply evaluates to itself; and the second operand expression is itself a primitive procedure call, which we can evalu
Procedure Rule. So, there is a hidden recursive equality to our new rule: namely, the notion of "evaluating operand expressions" may itself require the use of other evaluation rules, including the Primitive Procedure Rule.
Naming things with Define
In order to give names to objects, we use the expression define. As an example,
(define a 10)
a
(* a 2)
The Scheme interpreter should respond by printing out A, 10, and 20. The first expression tells the Scheme interpreter that henceforth a will be another name for the number 10. In evaluating the second expression, the interpreter evalua
returns the object that a names, 10. In the final expression, we are using the primitive procedure *, so the interpreter has to employ our Primitive Procedure Rule; since the first operand, a, evaluates to 10 and the second opera
value is 20.
Evaluating Symbols
In Scheme, we also have a classified define as a special form. The syntax is as follows:
(define symbol expression)
The expression is evaluated as follows: first evaluate the expression following the symbol, and then bind the symbol to the resulting value. Symbols can be bound to data objects (like numbers). To evaluate a symbol, see if it is bound t
that object; if not, signal an error.
Example...
(define a 20)
(define b 2)
(/ a b) = 10
Using define to Create New Procedures
(define (double number)
(* 2 number))
The example above is a procedure named double. It is called on one argument, a number, and returns the value of that number multiplied by two.
Defining Abstract Objects
This example demonstrates a syntactic extension that facilitates the definition of simple abstract objects. This facility has unlimited potential as the basis for a complete object-oriented subsystem in Scheme.
Abstract objects are similar to basic data structures such as pairs and vectors. Rather than being manipulated via access and assignment operators, however, abstract objects respond to messages. The valid messages and the actions
A particular type of abstract object is defined with define-object, which has the general form
(define-object (name var1 ...)
((var2 val) ...)
((msg action) ...))
The first set of bindings ((var2 val) ...) may be omitted. define-object defines a procedure that is called to create new abstract objects of the given type. This procedure is called name, and the arguments to this procedure become the
The syntactic form send-message is used to send messages to abstract objects. (send-message object msg arg ...) sends object the message msg with arguments arg .... When an object receives a message, the arg ... become the parameters to
The following example should help to clarify how abstract objects are defined and used. The example is a simple kons object that is similar to Scheme's built-in pair object type, except that to access or assign its fields requires sending it messages.
(define-object (kons kar kdr)
((get-car (lambda () kar))
(get-cdr (lambda () kdr))
(set-car! (lambda (x) (set! kar x)))
(set-cdr! (lambda (x) (set! kdr x)))))
(define p (kons 'a 'b))
(send-message p get-car) a
(send-message p get-cdr) b
(send-message p set-cdr! 'c)
(send-message p get-cdr) c
The implementation of define-object is straightforward. The object definition is transformed into a definition of the object creation procedure. This procedure is
the value of a lambda expression whose arguments are those specified in the definition. The body of the lambda consists of a let* expression to bind the local variables and a letrec expression to bind the message names to the action pro
All the above examples throw some light on the type of abstraction and encapsulation the functional language Scheme supports.
Comparison and Contrast between Scheme and Lisp
Common Lisp is used for comparison with Scheme.
Handling of Data Objects
In handling of data objects, both Scheme and Lisp adopts lexical scoping and first-class handling. Lexical scoping allows a compiler to determine before program evaluation the scope of all bindings and the the binding which each idetifier reference resolves. However, this does not mean that a compiler can determine the values of all variables since the actual values are not computed until the program executes. Lexical scoping and first class handling help create programs that are modular, easy to read, easy to maintain and reliable.
The data objects supported by Lisp are Numbers, Characters, Symbols, Lists, Arrays, Hash Tables, Readtables,Packages, Pathnames, Streams, Random-states and Structures. Additional categories of data objects are Classes, Methods and Generic functions.
Handling of Sequence Control
Basic invocation to function (Lisp) and procedure application(Scheme) has no name. Any list that has no other interpretation as a macro call or special form is taken to be a function call or an procedure application call accodingly. Proceudres in Scheme are first class data objects like strings or numbers and variables are bound to procedures in the same way they are nound to other objects.
Example of Lisp function invocation:
LISP: ( apply # '+' ( )) => 0
Both Scheme and Lisp has implicit simple sequence control structure Scheme uses the construct begin for simple sequencing. LISP uses the implict progn construct.The syntax of the progn construct in Lisp:.
Syntax: progn (form)*
The progn construct takes a number of forms and evaluates them sequentially in order from left to right. The value of the last form is returned by the progn form.
Scheme supports the definition of arbitrary control structures with continuations. A continuation is a procedure that embodies the remainder of a program at a given point in the program. When a continuation is invoked, the program immediately continues from that point. A continuation may be obtained at any time during the execution of a program. As with other procedures, a continuation is a first-class object and may be invoked at any time after its creation. Continuations allow the implementation of complex control mechanisms including explicit backtracking, multithreading, and coroutines.
In addition, Sheme has a special case of recursion called tail recursion. Tail recursion is used to express iteration or looping. A tail call recursion occurs when a procedure recursively tail calls itself, directly or indeirectly. Scheme implementations are required to implement tail calss as jumps(gotoS) so the storage overhead normally associat. When a continuation is invoked, the program immediately continues from that point. A continuation may be obtained at . As with other procedures, a continuation is a first-class object and may be invoked at any time after its creation. , multithreading, and coroutines. ed with recursion is avoided.
However, Lisp does not support continuations or require proper treatment of tail calls, but it does support several less general control structures not found in Scheme. While the two languages are similar, Lisp includes more specialized operators, while Scheme includes more general-purpose building blocks out of which such operators (and others) may be built.
Handling of Subprogram
Like Scheme, LISP subprograms might not always be named. Identifiers for subprograms in both languages can be bounded either globally or locally. Both languages allow the creation of large and complex subprograms from small and simple subprograms. Calls to subprograms may be simple call-return or recursive calls although Scheme has a special case of recursion tail recursion. Unlike Scheme, versions of LISP after 1988 do not allow lambda-expression to be used as function arguments.
Common LISP is one of the versions of LISP. Like Scheme but unlike the earlier LISP languages, Common Lisp adopted lexical scoping and first-class procedures. Although Common LISP allows first-class procedures, it discourages the use of procedures as first-class objects by it maintains a separate namespace for procedure variables. Also, Common Lisp does not support continuations or require proper treatment of tail calls, but it does support several less general control structures not found in Scheme. While the two languages are similar, Common Lisp includes more specialized operators, while Scheme includes more general-purpose building blocks out of which such operators and others may be built
Storage Management
Both Scheme and LISP allows for dynamic allocation of objects or data values. In both languages, storage is reclaimed periodically. Scheme uses weak pairs and guardians to help with storage management while LISP manage storage by sacrificing one type of storage for another when program gets very large and complex. Unlike Scheme which uses a heap to store objects, LISP uses linked lists that make storage management easier.
Handling of abstraction and encapsulation:
Functional languages such as (basic) LISP do not have advanced data types such as classes. The linked list structure as well as the rest of the data structures used in functional languages does not support data abstractions like in obje
Current day implementations of LISP, however, have extensions, which allow LISP programs to be more statement-oriented.
Common LISP is used for comparison with Scheme.
The use of Structures in Common LISP provides some kind of encapsulation.
The essential differences between MIT Scheme's define-structure and Common Lisp's defstruct are:
By default, named structures are tagged with a unique object of some kind. In Common Lisp, the structures are tagged with symbols. This depends on the Common Lisp package system to help generate unique tags; MIT Scheme has no such way to generate uniq
Defining New Type Specifiers in LISP
New type specifiers can come into existence in two ways. First, defining a new structure type with defstruct automatically causes the name of the structure to be a new type specifier symbol. Second, the deftype special form can be used
deftype name lambda-list [[{declaration}* | doc-string]] {form}*
Here are some examples of the use of deftype:
(deftype mod (n) `(integer 0 (,n)))
(deftype list () '(or null cons))
The Common Lisp Object System (CLOS) is an object-oriented extension to Common Lisp. It is based on generic functions, multiple inheritance, declarative method combination, and a meta-object protocol.
The fundamental objects of the Common Lisp Object System are classes, instances, generic functions, and methods.
This extension of Common LISP provides for abstraction and encapsulation, which is, absent in SCHEME.
http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node1.html
http://www.cs.indiana.edu/chezscheme/csug/index.html
http://www.cs.indiana.edu/chezscheme/tspl2d/index.html
Dybuig, R. Kent. The Scheme Programming Language. New Jersey: Prentice-hall, Inc. 1987.
Wilensky, Robert. Common LISPcraft. New York: W. W. Norton & Company, Inc. 1986.