90 |
91 | 92 |
93 | 94 |
Notes
96 | {{footnotes}} 97 |100 | 101 |
Based on Structure and Interpretation of Computer Programs, a work at https://mitpress.mit.edu/sicp/.
102 |
103 |
├── .gitattributes ├── .gitignore ├── LICENSE ├── coding.js.readme.txt ├── content ├── 1-0-abstractions.content.html ├── 1-1-elements.content.html ├── 1-2-procedures.content.html ├── 1-3-hop.content.html ├── 2-0-building-abstractions-with-data.content.html ├── 2-1-data.content.html ├── 2-2-closure.content.html ├── 2-3-symbolic.content.html ├── 2-4-representation.content.html ├── 2-5-generic.content.html ├── 3-0-modularity-objects-and-state.content.html ├── 3-1-assignment.content.html ├── 3-2-environment.content.html ├── 3-3-modeling.content.html ├── 3-4-concurrency.content.html ├── 3-5-streams.content.html ├── 4-0-metalinguistic-abstraction.content.html ├── 4-1-metacircular.content.html ├── 4-2-lazy.content.html ├── 4-3-nondeterministic.content.html ├── 4-4-logic.content.html ├── 5-0-computing-with-register-machines.content.html ├── 5-1-register.content.html ├── 5-2-simulator.content.html ├── 5-3-storage.content.html ├── 5-4-explicit-control.content.html └── 5-5-compilation.content.html ├── css ├── default.css ├── footnotes.css ├── isicp.css ├── lambda.css └── theme.css ├── images ├── book-Z-G-D-16.gif ├── ch1-Z-G-1.gif ├── ch1-Z-G-10.gif ├── ch1-Z-G-13.gif ├── ch1-Z-G-6.gif ├── ch1-Z-G-7.gif ├── ch2-Z-G-11.gif ├── ch2-Z-G-12.gif ├── ch2-Z-G-13.gif ├── ch2-Z-G-15.gif ├── ch2-Z-G-16.gif ├── ch2-Z-G-17.gif ├── ch2-Z-G-23.gif ├── ch2-Z-G-24.gif ├── ch2-Z-G-25.gif ├── ch2-Z-G-26.gif ├── ch2-Z-G-27.gif ├── ch2-Z-G-28.gif ├── ch2-Z-G-29.gif ├── ch2-Z-G-30.gif ├── ch2-Z-G-31.gif ├── ch2-Z-G-32.gif ├── ch2-Z-G-33.gif ├── ch2-Z-G-34.gif ├── ch2-Z-G-35.gif ├── ch2-Z-G-36.gif ├── ch2-Z-G-37.gif ├── ch2-Z-G-38.gif ├── ch2-Z-G-39.gif ├── ch2-Z-G-40.gif ├── ch2-Z-G-41.gif ├── ch2-Z-G-42.gif ├── ch2-Z-G-51.gif ├── ch2-Z-G-52.gif ├── ch2-Z-G-53.gif ├── ch2-Z-G-54.gif ├── ch2-Z-G-59.gif ├── ch2-Z-G-6.gif ├── ch2-Z-G-62.gif ├── ch2-Z-G-63.gif ├── ch2-Z-G-64.gif ├── ch2-Z-G-65.gif ├── ch2-Z-G-66.gif ├── ch2-Z-G-67.gif ├── ch3-Z-G-1.gif ├── ch3-Z-G-10.gif ├── ch3-Z-G-11.gif ├── ch3-Z-G-12.gif ├── ch3-Z-G-13.gif ├── ch3-Z-G-14.gif ├── ch3-Z-G-15.gif ├── ch3-Z-G-16.gif ├── ch3-Z-G-17.gif ├── ch3-Z-G-18.gif ├── ch3-Z-G-19.gif ├── ch3-Z-G-2.gif ├── ch3-Z-G-20.gif ├── ch3-Z-G-21.gif ├── ch3-Z-G-22.gif ├── ch3-Z-G-23.gif ├── ch3-Z-G-24.gif ├── ch3-Z-G-25.gif ├── ch3-Z-G-26.gif ├── ch3-Z-G-3.gif ├── ch3-Z-G-30.gif ├── ch3-Z-G-4.gif ├── ch3-Z-G-5.gif ├── ch3-Z-G-6.gif ├── ch3-Z-G-7.gif ├── ch3-Z-G-8.gif ├── ch3-Z-G-9.gif ├── ch4-Z-G-1.gif ├── chevron-left.svg ├── chevron-right.svg ├── chevron-up.svg ├── envelope.svg ├── file-edit.svg ├── film.svg ├── home.svg ├── icons.source.md ├── list.svg ├── photo.svg ├── screenshot.png ├── video.svg └── zoom-screenshot.png ├── index.html ├── js ├── TOC.js ├── app.js ├── footnotes.js ├── jquery.min.js └── lambda │ ├── lambda.js │ └── test-lambda.html ├── lambda.html ├── readme.md ├── render.py ├── render_all.sh ├── sicp_raw ├── sicp.html ├── sicp.texi └── texi.py └── template.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # render scripts choke on \r 5 | *.html text eol=lf 6 | *.sh text eol=lf 7 | 8 | # Standard to msysgit 9 | *.doc diff=astextplain 10 | *.DOC diff=astextplain 11 | *.docx diff=astextplain 12 | *.DOCX diff=astextplain 13 | *.dot diff=astextplain 14 | *.DOT diff=astextplain 15 | *.pdf diff=astextplain 16 | *.PDF diff=astextplain 17 | *.rtf diff=astextplain 18 | *.RTF diff=astextplain 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python specific 2 | *.pyc 3 | __pycache__ 4 | 5 | ## generic files to ignore 6 | *~ 7 | *.lock 8 | *.DS_Store 9 | *.swp 10 | *.out 11 | */Thumbs.db 12 | 13 | [0-9]*.html 14 | 15 | coding-js 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015 Li Xuanji 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /coding.js.readme.txt: -------------------------------------------------------------------------------- 1 | Put the coding.js/ folder here to enable interactive features. 2 | Clone it from this repository: 3 | https://github.com/yuanchenyang/coding-js -------------------------------------------------------------------------------- /content/1-0-abstractions.content.html: -------------------------------------------------------------------------------- 1 | {{title}} 2 |
The acts of the mind, wherein it exerts its power over simple ideas, are chiefly these three: 1. Combining several simple ideas into one compound one, and thus all complex ideas are made. 2. The second is bringing two ideas, whether simple or complex, together, and setting them by one another so as to take a view of them at once, without uniting them into one, by which it gets all its ideas of relations. 3. The third is separating them from all other ideas that accompany them in their real existence: this is called abstraction, and thus all its general ideas are made. 16 | 17 |19 | 20 |John Locke, An Essay Concerning Human Understanding (1690) 18 |
We are about to study the idea of a computational process. Computational processes are abstract beings that inhabit computers. As they evolve, processes manipulate other abstract things called data. The evolution of a process is directed by a pattern of rules called a program. People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells. 21 | 22 |
A computational process is indeed much like a sorcerer's idea of a spirit. It cannot be seen or touched. It is not composed of matter at all. However, it is very real. It can perform intellectual work. It can answer questions. It can affect the world by disbursing money at a bank or by controlling a robot arm in a factory. The programs we use to conjure processes are like a sorcerer's spells. They are carefully composed from symbolic expressions in arcane and esoteric programming languages that prescribe the tasks we want our processes to perform. 23 | 24 |
A computational process, in a correctly working computer, executes programs precisely and accurately. Thus, like the sorcerer's apprentice, novice programmers must learn to understand and to anticipate the consequences of their conjuring. Even small errors (usually called bugs or glitches) in programs can have complex and unanticipated consequences. 25 | 26 |
Fortunately, learning to program is considerably less dangerous than learning sorcery, because the spirits we deal with are conveniently contained in a secure way. Real-world programming, however, requires care, expertise, and wisdom. A small bug in a computer-aided design program, for example, can lead to the catastrophic collapse of an airplane or a dam or the self-destruction of an industrial robot. 27 | 28 |
Master software engineers have the ability to organize programs so that they can be reasonably sure that the resulting processes will perform the tasks intended. They can visualize the behavior of their systems in advance. They know how to structure programs so that unanticipated problems do not lead to catastrophic consequences, and when problems do arise, they can debug their programs. Well-designed computational systems, like well-designed automobiles or nuclear reactors, are designed in a modular manner, so that the parts can be constructed, replaced, and debugged separately. 29 | 30 |
We need an appropriate language for describing processes, and we will use for this purpose the programming language Lisp. Just as our everyday thoughts are usually expressed in our natural language (such as English, French, or Japanese), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in Lisp. Lisp was invented in the late 1950s as a formalism for reasoning about the use of certain kinds of logical expressions, called recursion equations, as a model for computation. The language was conceived by John McCarthy and is based on his paper "Recursive Functions of Symbolic Expressions and Their Computation by Machine" (McCarthy 1960). 33 | 34 |
Despite its inception as a mathematical formalism, Lisp is a practical programming language. A Lisp interpreter is a machine that carries out processes described in the Lisp language. The first Lisp interpreter was implemented by McCarthy with the help of colleagues and students in the Artificial Intelligence Group of the MIT Research Laboratory of Electronics and in the MIT Computation Center.1 Lisp, whose name is an acronym for LISt Processing, was designed to provide symbol-manipulating capabilities for attacking programming problems such as the symbolic differentiation and integration of algebraic expressions. It included for this purpose new data objects known as atoms and lists, which most strikingly set it apart from all other languages of the period. 35 | 36 |
Lisp was not the product of a concerted design effort. Instead, it evolved informally in an experimental manner in response to users' needs and to pragmatic implementation considerations. Lisp's informal evolution has continued through the years, and the community of Lisp users has traditionally resisted attempts to promulgate any ``official'' definition of the language. This evolution, together with the flexibility and elegance of the initial conception, has enabled Lisp, which is the second oldest language in widespread use today (only Fortran is older), to continually adapt to encompass the most modern ideas about program design. Thus, Lisp is by now a family of dialects, which, while sharing most of the original features, may differ from one another in significant ways. The dialect of Lisp used in this book is called Scheme.2 37 | 38 |
Because of its experimental character and its emphasis on symbol manipulation, Lisp was at first very inefficient for numerical computations, at least in comparison with Fortran. Over the years, however, Lisp compilers have been developed that translate programs into machine code that can perform numerical computations reasonably efficiently. And for special applications, Lisp has been used with great effectiveness.3 Although Lisp has not yet overcome its old reputation as hopelessly inefficient, Lisp is now used in many applications where efficiency is not the central concern. For example, Lisp has become a language of choice for operating-system shell languages and for extension languages for editors and computer-aided design systems. 39 | 40 |
If Lisp is not a mainstream language, why are we using it as the framework for our discussion of programming? Because the language possesses unique features that make it an excellent medium for studying important programming constructs and data structures and for relating them to the linguistic features that support them. The most significant of these features is the fact that Lisp descriptions of processes, called procedures, can themselves be represented and manipulated as Lisp data. The importance of this is that there are powerful program-design techniques that rely on the ability to blur the traditional distinction between ``passive'' data and ``active'' processes. As we shall discover, Lisp's flexibility in handling procedures as data makes it one of the most convenient languages in existence for exploring these techniques. The ability to represent procedures as data also makes Lisp an excellent language for writing programs that must manipulate other programs as data, such as the interpreters and compilers that support computer languages. Above and beyond these considerations, programming in Lisp is great fun. 41 | 42 | @@ 43 | 44 | {{footnotes}} 45 | 46 |
1 The Lisp 1 Programmer's Manual appeared in 1960, and the Lisp 1.5 Programmer's Manual (McCarthy 1965) was published in 1962. The early history of Lisp is described in McCarthy 1978. 48 |
2 52 | The two dialects in which most major Lisp programs of the 1970s were written are MacLisp (Moon 1978; Pitman 1983), developed at the MIT Project MAC, and Interlisp (Teitelman 1974), developed at Bolt Beranek and Newman Inc. and the Xerox Palo Alto Research Center. Portable Standard Lisp (Hearn 1969; Griss 1981) was a Lisp dialect designed to be easily portable between different machines. MacLisp spawned a number of subdialects, such as Franz Lisp, which was developed at the University of California at Berkeley, and Zetalisp (Moon 1981), which was based on a special-purpose processor designed at the MIT Artificial Intelligence Laboratory to run Lisp very efficiently. The Lisp dialect used in this book, called Scheme (Steele 1975), was invented in 1975 by Guy Lewis Steele Jr. and Gerald Jay Sussman of the MIT Artificial Intelligence Laboratory and later reimplemented for instructional use at MIT. Scheme became an IEEE standard in 1990 (IEEE 1990). The Common Lisp dialect (Steele 1982, Steele 1990) was developed by the Lisp community to combine features from the earlier Lisp dialects to make an industrial standard for Lisp. Common Lisp became an ANSI standard in 1994 (ANSI 1994). 53 |
3 57 | One such special application was a breakthrough computation of scientific importance -- an integration of the motion of the Solar System that extended previous results by nearly two orders of magnitude, and demonstrated that the dynamics of the Solar System is chaotic. This computation was made possible by new integration algorithms, a special-purpose compiler, and a special-purpose computer all implemented with the aid of software tools written in Lisp (Abelson et al. 1992; Sussman and Wisdom 1992). 58 |
We now come to the decisive step of mathematical abstraction: we forget about what the symbols stand for. ...[The mathematician] need not be idle; there are many operations which he may carry out with these symbols, without ever having to look at the things they stand for. 18 | 19 |21 | 22 |Hermann Weyl, The Mathematical Way of Thinking 20 |
We concentrated in chapter 1 on computational processes and on the role of procedures in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to combine procedures to form compound procedures through composition, conditionals, and the use of parameters, and how to abstract procedures by using define. We saw that a procedure can be regarded as a pattern for the local evolution of a process, and we classified, reasoned about, and performed simple algorithmic analyses of some common patterns for processes as embodied in procedures. We also saw that higher-order procedures enhance the power of our language by enabling us to manipulate, and thereby to reason in terms of, general methods of computation. This is much of the essence of programming. 23 | 24 |
In this chapter we are going to look at more complex data. All the procedures in chapter 1 operate on simple numerical data, and simple data are not sufficient for many of the problems we wish to address using computation. Programs are typically designed to model complex phenomena, and more often than not one must construct computational objects that have several parts in order to model real-world phenomena that have several aspects. Thus, whereas our focus in chapter 1 was on building abstractions by combining procedures to form compound procedures, we turn in this chapter to another key aspect of any programming language: the means it provides for building abstractions by combining data objects to form compound data. 25 | 26 |
Why do we want compound data in a programming language? For the same reasons that we want compound procedures: to elevate the conceptual level at which we can design our programs, to increase the modularity of our designs, and to enhance the expressive power of our language. Just as the ability to define procedures enables us to deal with processes at a higher conceptual level than that of the primitive operations of the language, the ability to construct compound data objects enables us to deal with data at a higher conceptual level than that of the primitive data objects of the language. 27 | 28 |
Consider the task of designing a system to perform arithmetic with rational numbers. We could imagine an operation add-rat that takes two rational numbers and produces their sum. In terms of simple data, a rational number can be thought of as two integers: a numerator and a denominator. Thus, we could design a program in which each rational number would be represented by two integers (a numerator and a denominator) and where add-rat would be implemented by two procedures (one producing the numerator of the sum and one producing the denominator). But this would be awkward, because we would then need to explicitly keep track of which numerators corresponded to which denominators. In a system intended to perform many operations on many rational numbers, such bookkeeping details would clutter the programs substantially, to say nothing of what they would do to our minds. It would be much better if we could "glue together" a numerator and denominator to form a pair -- a compound data object -- that our programs could manipulate in a way that would be consistent with regarding a rational number as a single conceptual unit. 29 | 30 |
The use of compound data also enables us to increase the modularity of our programs. If we can manipulate rational numbers directly as objects in their own right, then we can separate the part of our program that deals with rational numbers per se from the details of how rational numbers may be represented as pairs of integers. The general technique of isolating the parts of a program that deal with how data objects are represented from the parts of a program that deal with how data objects are used is a powerful design methodology called data abstraction. We will see how data abstraction makes programs much easier to design, maintain, and modify. 31 | 32 |
The use of compound data leads to a real increase in the expressive power of our programming language. Consider the idea of forming a "linear combination" ax + by. We might like to write a procedure that would accept a, b, x, and y as arguments and return the value of ax + by. This presents no difficulty if the arguments are to be numbers, because we can readily define the procedure 33 | 34 |
But suppose we are not concerned only with numbers. Suppose we would like to express, in procedural terms, the idea that one can form linear combinations whenever addition and multiplication are defined -- for rational numbers, complex numbers, polynomials, or whatever. We could express this as a procedure of the form 43 | 44 |
where add and mul are not the primitive procedures + and * but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the arguments a, b, x, and y. The key point is that the only thing linear-combination should need to know about a, b, x, and y is that the procedures add and mul will perform the appropriate manipulations. From the perspective of the procedure linear-combination, it is irrelevant what a, b, x, and y are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: Without this, there is no way for a procedure such as linear-combination to pass its arguments along to add and mul without having to know their detailed structure.1 We begin this chapter by implementing the rational-number arithmetic system mentioned above. This will form the background for our discussion of compound data and data abstraction. As with compound procedures, the main issue to be addressed is that of abstraction as a technique for coping with complexity, and we will see how data abstraction enables us to erect suitable abstraction barriers between different parts of a program. 53 | 54 |
We will see that the key to forming compound data is that a programming language should provide some kind of "glue" so that data objects can be combined to form more complex data objects. There are many possible kinds of glue. Indeed, we will discover how to form compound data using no special "data" operations at all, only procedures. This will further blur the distinction between "procedure" and "data" which was already becoming tenuous toward the end of chapter 1. We will also explore some conventional techniques for representing sequences and trees. One key idea in dealing with compound data is the notion of closure -- that the glue we use for combining data objects should allow us to combine not only primitive data objects, but compound data objects as well. Another key idea is that compound data objects can serve as conventional interfaces for combining program modules in mix-and-match ways. We illustrate some of these ideas by presenting a simple graphics language that exploits closure. 55 | 56 |
We will then augment the representational power of our language by introducing symbolic expressions -- data whose elementary parts can be arbitrary symbols rather than only numbers. We explore various alternatives for representing sets of objects. We will find that, just as a given numerical function can be computed by many different computational processes, there are many ways in which a given data structure can be represented in terms of simpler objects, and the choice of representation can have significant impact on the time and space requirements of processes that manipulate the data. We will investigate these ideas in the context of symbolic differentiation, the representation of sets, and the encoding of information. 57 | 58 |
Next we will take up the problem of working with data that may be represented differently by different parts of a program. This leads to the need to implement generic operations, which must handle many different types of data. Maintaining modularity in the presence of generic operations requires more powerful abstraction barriers than can be erected with simple data abstraction alone. In particular, we introduce data-directed programming as a technique that allows individual data representations to be designed in isolation and then combined additively (i.e., without modification). To illustrate the power of this approach to system design, we close the chapter by applying what we have learned to the implementation of a package for performing symbolic arithmetic on polynomials, in which the coefficients of the polynomials can be integers, rational numbers, complex numbers, and even other polynomials. 59 | 60 | {{footnotes}} 61 | @@ 62 | -------------------------------------------------------------------------------- /content/3-0-modularity-objects-and-state.content.html: -------------------------------------------------------------------------------- 1 | {{title}} 2 |
17 |26 | 27 |18 |
(Even while it changes, it stands still.) 19 | 20 |Heraclitus 21 | 22 |
Plus ça change, plus c'est la même chose. 23 | 24 |
Alphonse Karr 25 |
The preceding chapters introduced the basic elements from which programs are made. We saw how primitive procedures and primitive data are combined to construct compound entities, and we learned that abstraction is vital in helping us to cope with the complexity of large systems. But these tools are not sufficient for designing programs. Effective program synthesis also requires organizational principles that can guide us in formulating the overall design of a program. In particular, we need strategies to help us structure large systems so that they will be modular, that is, so that they can be divided ``naturally'' into coherent parts that can be separately developed and maintained. 30 | 31 |
One powerful design strategy, which is particularly appropriate to the construction of programs for modeling physical systems, is to base the structure of our programs on the structure of the system being modeled. For each object in the system, we construct a corresponding computational object. For each system action, we define a symbolic operation in our computational model. Our hope in using this strategy is that extending the model to accommodate new objects or new actions will require no strategic changes to the program, only the addition of the new symbolic analogs of those objects or actions. If we have been successful in our system organization, then to add a new feature or debug an old one we will have to work on only a localized part of the system. 32 | 33 |
To a large extent, then, the way we organize a large program is dictated by our perception of the system to be modeled. In this chapter we will investigate two prominent organizational strategies arising from two rather different ``world views'' of the structure of systems. The first organizational strategy concentrates on objects, viewing a large system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams of information that flow in the system, much as an electrical engineer views a signal-processing system. 34 | 35 |
Both the object-based approach and the stream-processing approach raise significant linguistic issues in programming. With objects, we must be concerned with how a computational object can change and yet maintain its identity. This will force us to abandon our old substitution model of computation (section 1.1.5) in favor of a more mechanistic but less theoretically tractable environment model of computation. The difficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs. The stream approach can be most fully exploited when we decouple simulated time in our model from the order of the events that take place in the computer during evaluation. We will accomplish this using a technique known as delayed evaluation. 36 | 37 | {{footnotes}} 38 | @@ 39 | -------------------------------------------------------------------------------- /content/3-2-environment.content.html: -------------------------------------------------------------------------------- 1 | {{title}} 2 |
When we introduced compound procedures in Chapter 1, we used the substitution model of evaluation (section 1.1.5) to define what is meant by applying a procedure to arguments: 17 | 18 |
19 | 20 |24 | 25 |To apply a compound procedure to arguments, evaluate the body of the procedure 21 | with each formal parameter replaced by the corresponding argument. 22 | 23 |
Once we admit assignment into our programming language, such a definition is no longer adequate. In particular, section 3.1.3 argued that, in the presence of assignment, a variable can no longer be considered to be merely a name for a value. Rather, a variable must somehow designate a "place" in which values can be stored. In our new model of evaluation, these places will be maintained in structures called environments. 26 | 27 |
An environment is a sequence of frames . Each frame is a table (possibly empty) of bindings , which associate variable names with their corresponding values. (A single frame may contain at most one binding for any variable.) Each frame also has a pointer to its enclosing environment , unless, for the purposes of discussion, the frame is considered to be global . The value of a variable with respect to an environment is the value given by the binding of the variable in the first frame in the environment that contains a binding for that variable. If no frame in the sequence specifies a binding for the variable, then the variable is said to be unbound in the environment. 28 | 29 |
Figure 3.1: A simple environment structure. 32 |
Figure 3-1 shows a simple environment structure consisting of three frames, labeled I, II, and III. In the diagram, A, B, C, and D are pointers to environments. C and D point to the same environment. The variables z and x are bound in frame II, while y and x are bound in frame I. The value of x in environment D is 3. The value of x with respect to environment B is also 3. This is determined as follows: We examine the first frame in the sequence (frame III) and do not find a binding for x, so we proceed to the enclosing environment D and find the binding in frame I. On the other hand, the value of x in environment A is 7, because the first frame in the sequence (frame II) contains a binding of x to 7. With respect to environment A, the binding of x to 7 in frame II is said to shadow the binding of x to 3 in frame I. 35 | 36 |
The environment is crucial to the evaluation process, because it determines the context in which an expression should be evaluated. Indeed, one could say that expressions in a programming language do not, in themselves, have any meaning. Rather, an expression acquires a meaning only with respect to some environment in which it is evaluated. Even the interpretation of an expression as straightforward as (+ 1 1) depends on an understanding that one is operating in a context in which + is the symbol for addition. Thus, in our model of evaluation we will always speak of evaluating an expression with respect to some environment. To describe interactions with the interpreter, we will suppose that there is a global environment, consisting of a single frame (with no enclosing environment) that includes values for the symbols associated with the primitive procedures. For example, the idea that + is the symbol for addition is captured by saying that the symbol + is bound in the global environment to the primitive addition procedure. 37 | 38 | 39 |
The overall specification of how the interpreter evaluates a combination remains the same as when we first introduced it in section 1.1.3: 42 | 43 |
To evaluate a combination: 44 | 45 |
Evaluate the subexpressions of the combination.12 49 |
Apply the value of the operator subexpression to the values of the operand subexpressions. 53 |
The environment model of evaluation replaces the substitution model in specifying what it means to apply a compound procedure to arguments. 57 | 58 |
In the environment model of evaluation, a procedure is always a pair consisting of some code and a pointer to an environment. Procedures are created in one way only: by evaluating a lambda expression. This produces a procedure whose code is obtained from the text of the lambda expression and whose environment is the environment in which the lambda expression was evaluated to produce the procedure. For example, consider the procedure definition 59 | 60 |
evaluated in the global environment. The procedure definition syntax is just syntactic sugar for an underlying implicit lambda expression. It would have been equivalent to have used 69 | 70 |
which evaluates (lambda (x) (* x x)) and binds square to the resulting value, all in the global environment. 79 | 80 |
Figure 3-2 shows the result of evaluating this define expression. The procedure object is a pair whose code specifies that the procedure has one formal parameter, namely x, and a procedure body (* x x). The environment part of the procedure is a pointer to the global environment, since that is the environment in which the lambda expression was evaluated to produce the procedure. A new binding, which associates the procedure object with the symbol square, has been added to the global frame. In general, define creates definitions by adding bindings to frames. 81 | 82 |
Figure 3.2: Environment structure produced by evaluating (define (square x) (* x x)) in the global environment. 85 |
Now that we have seen how procedures are created, we can describe how procedures are applied. The environment model specifies: To apply a procedure to arguments, create a new environment containing a frame that binds the parameters to the values of the arguments. The enclosing environment of this frame is the environment specified by the procedure. Now, within this new environment, evaluate the procedure body. 88 | 89 |
To show how this rule is followed, Figure 3-3 illustrates the environment structure created by evaluating the expression (square 5) in the global environment, where square is the procedure generated in Figure 3-2. Applying the procedure results in the creation of a new environment, labeled E1 in the figure, that begins with a frame in which x, the formal parameter for the procedure, is bound to the argument 5. The pointer leading upward from this frame shows that the frame's enclosing environment is the global environment. The global environment is chosen here, because this is the environment that is indicated as part of the square procedure object. Within E1, we evaluate the body of the procedure, (* x x). Since the value of x in E1 is 5, the result is (* 5 5), or 25. 90 | 91 |
Figure 3.3: Environment created by evaluating (square 5) in the global environment. 94 |
The environment model of procedure application can be summarized by two rules: 97 | 98 |
A procedure object is applied to a set of arguments by constructing a frame, binding the formal parameters of the procedure to the arguments of the call, and then evaluating the body of the procedure in the context of the new environment constructed. The new frame has as its enclosing environment the environment part of the procedure object being applied. 102 |
A procedure is created by evaluating a lambda expression relative to a given environment. The resulting procedure object is a pair consisting of the text of the lambda expression and a pointer to the environment in which the procedure was created. 106 |
We also specify that defining a symbol using define creates a binding in the current environment frame and assigns to the symbol the indicated value.13Finally, we specify the behavior of set!, the operation that forced us to introduce the environment model in the first place. Evaluating the expression (set! <variable> <value>) in some environment locates the binding of the variable in the environment and changes that binding to indicate the new value. That is, one finds the first frame in the environment that contains a binding for the variable and modifies that frame. If the variable is unbound in the environment, then set! signals an error. 110 | 111 |
These evaluation rules, though considerably more complex than the substitution model, are still reasonably straightforward. Moreover, the evaluation model, though abstract, provides a correct description of how the interpreter evaluates expressions. In Chapter 4 we shall see how this model can serve as a blueprint for implementing a working interpreter. The following sections elaborate the details of the model by analyzing some illustrative programs. 112 | 113 | 114 |
When we introduced the substitution model in section 1.1.5 we showed how the combination (f 5) evaluates to 136, given the following procedure definitions: 117 | 118 |
We can analyze the same example using the environment model. Figure 3-4 shows the three procedure objects created by evaluating the definitions of f, square, and sum-of-squares in the global environment. Each procedure object consists of some code, together with a pointer to the global environment. 133 | 134 |
Figure 3.4: Procedure objects in the global frame. 137 |
In Figure 3-5 we see the environment structure created by evaluating the expression (f 5). The call to f creates a new environment E1 beginning with a frame in which a, the formal parameter of f, is bound to the argument 5. In E1, we evaluate the body of f: 140 | 141 |
Figure 3.5: Environments created by evaluating 151 |
To evaluate this combination, we first evaluate the subexpressions. The first subexpression, sum-of-squares, has a value that is a procedure object. (Notice how this value is found: We first look in the first frame of E1, which contains no binding for sum-of-squares. Then we proceed to the enclosing environment, i.e. the global environment, and find the binding shown in Figure 3-4.) The other two subexpressions are evaluated by applying the primitive operations + and * to evaluate the two combinations (+ a 1) and (* a 2) to obtain 6 and 10, respectively. 154 | 155 |
Now we apply the procedure object sum-of-squares to the arguments 6 and 10. This results in a new environment E2 in which the formal parameters x and y are bound to the arguments. Within E2 we evaluate the combination (+ (square x) (square y)). This leads us to evaluate (square x), where square is found in the global frame and x is 6. Once again, we set up a new environment, E3, in which x is bound to 6, and within this we evaluate the body of square, which is (* x x). Also as part of applying sum-of-squares, we must evaluate the subexpression (square y), where y is 10. This second call to square creates another environment, E4, in which x, the formal parameter of square, is bound to 10. And within E4 we must evaluate (* x x). 156 | 157 |
The important point to observe is that each call to square creates a new environment containing a binding for x. We can see here how the different frames serve to keep separate the different local variables all named x. Notice that each frame created by square points to the global environment, since this is the environment indicated by the square procedure object. 158 | 159 |
After the subexpressions are evaluated, the results are returned. The values generated by the two calls to square are added by sum-of-squares, and this result is returned by f. Since our focus here is on the environment structures, we will not dwell on how these returned values are passed from call to call; however, this is also an important aspect of the evaluation process, and we will return to it in detail in Chapter 5. 160 | 161 |
Exercise 3.9: In section 1.2.1 we used the substitution model to analyze two procedures for computing factorials, a recursive version 163 | 164 |
and an iterative version 175 | 176 |
Show the environment structures created by evaluating (factorial 6) using each version of the factorial procedure.14 192 |
We can turn to the environment model to see how procedures and assignment can be used to represent objects with local state. As an example, consider the "withdrawal processor" from section 3.1.1 created by calling the procedure 198 | 199 |
Let us describe the evaluation of 212 | 213 |
followed by 221 | 222 |
Figure 3-6 shows the result of defining the make-withdraw procedure in the global environment. This produces a procedure object that contains a pointer to the global environment. So far, this is no different from the examples we have already seen, except that the body of the procedure is itself a lambda expression. 230 | 231 |
Figure 3.6: Result of defining make-withdraw in the global environment. 234 |
The interesting part of the computation happens when we apply the procedure make-withdraw to an argument: 237 | 238 |
We begin, as usual, by setting up an environment E1 in which the formal parameter balance is bound to the argument 100. Within this environment, we evaluate the body of make-withdraw, namely the lambda expression. This constructs a new procedure object, whose code is as specified by the lambda and whose environment is E1, the environment in which the lambda was evaluated to produce the procedure. The resulting procedure object is the value returned by the call to make-withdraw. This is bound to W1 in the global environment, since the define itself is being evaluated in the global environment. Figure 3-7 shows the resulting environment structure. 246 | 247 |
Figure 3.7: Result of evaluating (define W1 (make-withdraw 100)). 250 |
Now we can analyze what happens when W1 is applied to an argument: 253 | 254 |
We begin by constructing a frame in which amount, the formal parameter of W1, is bound to the argument 50. The crucial point to observe is that this frame has as its enclosing environment not the global environment, but rather the environment E1, because this is the environment that is specified by the W1 procedure object. Within this new environment, we evaluate the body of the procedure: 262 | 263 |
The resulting environment structure is shown in Figure 3-8. The expression being evaluated references both amount and balance. Amount will be found in the first frame in the environment, while balance will be found by following the enclosing-environment pointer to E1. 274 | 275 |
Figure 3.8: Environments created by applying the procedure object W1. 278 |
When the set! is executed, the binding of balance in E1 is changed. At the completion of the call to W1, balance is 50, and the frame that contains balance is still pointed to by the procedure object W1. The frame that binds amount (in which we executed the code that changed balance) is no longer relevant, since the procedure call that constructed it has terminated, and there are no pointers to that frame from other parts of the environment. The next time W1 is called, this will build a new frame that binds amount and whose enclosing environment is E1. We see that E1 serves as the ``place'' that holds the local state variable for the procedure object W1. Figure 3-9 shows the situation after the call to W1. 281 | 282 |
Figure 3.9: Environments after the call to W1. 285 |
Observe what happens when we create a second "withdraw" object by making 288 | another call to make-withdraw: 289 | 290 |
This produces the environment structure of Figure 3-10, which shows that W2 is a procedure object, that is, a pair with some code and an environment. The environment E2 for W2 was created by the call to make-withdraw. It contains a frame with its own local binding for balance. On the other hand, W1 and W2 have the same code: the code specified by the lambda expression in the body of make-withdraw.15 We see here why W1 and W2 behave as independent objects. Calls to W1 reference the state variable balance stored in E1, whereas calls to W2 reference the balance stored in E2. Thus, changes to the local state of one object do not affect the other object. 298 | 299 |
Figure 3.10: Using (define W2 (make-withdraw 100)) to create a second object. 302 |
Exercise 3.10: In the make-withdraw procedure, the local variable balance is created as a parameter of make-withdraw. We could also create the local state variable explicitly, using let, as follows: 306 | 307 |
Recall from section 1.3.2 that let is simply syntactic sugar for a procedure call: 321 | 322 |
is interpreted as an alternate syntax for 330 | 331 |
Use the environment model to analyze this alternate version of make-withdraw, drawing figures like the ones above to illustrate the interactions 339 | 340 |
Show that the two versions of make-withdraw create objects with the same behavior. How do the environment structures differ for the two versions? 350 |
Section 1.1.8 introduced the idea that procedures can have internal 356 | definitions, thus leading to a block structure as in the following procedure to 357 | compute square roots: 358 | 359 |
Now we can use the environment model to see why these internal definitions behave as desired. Figure 3-11 shows the point in the evaluation of the expression (sqrt 2) where the internal procedure good-enough? has been called for the first time with guess equal to 1. 384 | 385 |
Figure 3.11: Sqrt procedure with internal definitions. 388 |
Observe the structure of the environment. Sqrt is a symbol in the global environment that is bound to a procedure object whose associated environment is the global environment. When sqrt was called, a new environment E1 was formed, subordinate to the global environment, in which the parameter x is bound to 2. The body of sqrt was then evaluated in E1. Since the first expression in the body of sqrt is 391 | 392 |
evaluating this expression defined the procedure good-enough? in the environment E1. To be more precise, the symbol good-enough? was added to the first frame of E1, bound to a procedure object whose associated environment is E1. Similarly, improve and sqrt-iter were defined as procedures in E1. For conciseness, Figure 3-11 shows only the procedure object for good-enough?. 401 | 402 |
After the local procedures were defined, the expression (sqrt-iter 1.0) was evaluated, still in environment E1. So the procedure object bound to sqrt-iter in E1 was called with 1 as an argument. This created an environment E2 in which guess, the parameter of sqrt-iter, is bound to 1. Sqrt-iter in turn called good-enough? with the value of guess (from E2) as the argument for good-enough?. This set up another environment, E3, in which guess (the parameter of good-enough?) is bound to 1. Although sqrt-iter and good-enough? both have a parameter named guess, these are two distinct local variables located in different frames. Also, E2 and E3 both have E1 as their enclosing environment, because the sqrt-iter and good-enough? procedures both have E1 as their environment part. One consequence of this is that the symbol x that appears in the body of good-enough? will reference the binding of x that appears in E1, namely the value of x with which the original sqrt procedure was called. 403 | 404 |
The environment model thus explains the two key properties that make local procedure definitions a useful technique for modularizing programs: 405 | 406 |
The names of the local procedures do not interfere with names external to the enclosing procedure, because the local procedure names will be bound in the frame that the procedure creates when it is run, rather than being bound in the global environment. 410 |
The local procedures can access the arguments of the enclosing procedure, simply by using parameter names as free variables. This is because the body of the local procedure is evaluated in an environment that is subordinate to the evaluation environment for the enclosing procedure. 414 |
Exercise 3.11: In section 3.2.3 we saw how the environment model described the behavior of procedures with local state. Now we have seen how internal definitions work. A typical message-passing procedure contains both of these aspects. Consider the bank account procedure of section 3.1.1: 419 | 420 |
Show the environment structure generated by the sequence of interactions 442 | 443 |
Where is the local state for acc kept? Suppose we define another account 453 | 454 |
How are the local states for the two accounts kept distinct? Which parts of 462 | the environment structure are shared between acc and acc2? 463 |
12 Assignment introduces a subtlety into step 1 of the evaluation rule. As shown in exercise 3.8, the presence of assignment allows us to write expressions that will produce different values depending on the order in which the subexpressions in a combination are evaluated. Thus, to be precise, we should specify an evaluation order in step 1 (e.g., left to right or right to left). However, this order should always be considered to be an implementation detail, and one should never write programs that depend on some particular order. For instance, a sophisticated compiler might optimize a program by varying the order in which subexpressions are evaluated. 469 |
13 If there is already a binding for the variable in the current frame, then the binding is changed. This is convenient because it allows redefinition of symbols; however, it also means that define can be used to change values, and this brings up the issues of assignment without explicitly using set!. Because of this, some people prefer redefinitions of existing symbols to signal errors or warnings. 473 |
14 The environment model will not clarify our claim in section 1.2.1 that the interpreter can execute a procedure such as fact-iter in a constant amount of space using tail recursion. We will discuss tail recursion when we deal with the control structure of the interpreter in section 5.4. 477 |
15 Whether W1 and W2 share the same physical code stored in the computer, or whether they each keep a copy of the code, is a detail of the implementation. For the interpreter we implement in chapter 4, the code is in fact shared. 481 |
17 | ... It's in words that the magic is – Abracadabra, Open Sesame, and the rest – but the magic words in one story aren't magical in the next. The real magic is to understand which words work, and when, and for what; the trick is to learn the trick. 18 | ... And those words are made from the letters of our alphabet: a couple-dozen squiggles we can draw with the pen. This is the key! And the treasure, too, if we can only get our hands on it! It's as if – as if the key to the treasure is the treasure! 19 | 20 |22 | 23 |John Barth, Chimera 21 |
In our study of program design, we have seen that expert programmers control the complexity of their designs with the same general techniques used by designers of all complex systems. They combine primitive elements to form compound objects, they abstract compound objects to form higher-level building blocks, and they preserve modularity by adopting appropriate large-scale views of system structure. In illustrating these techniques, we have used Lisp as a language for describing processes and for constructing computational data objects and processes to model complex phenomena in the real world. However, as we confront increasingly complex problems, we will find that Lisp, or indeed any fixed programming language, is not sufficient for our needs. We must constantly turn to new languages in order to express our ideas more effectively. Establishing new languages is a powerful strategy for controlling complexity in engineering design; we can often enhance our ability to deal with a complex problem by adopting a new language that enables us to describe (and hence to think about) the problem in a different way, using primitives, means of combination, and means of abstraction that are particularly well suited to the problem at hand.1 24 | 25 |
Programming is endowed with a multitude of languages. There are physical languages, such as the machine languages for particular computers. These languages are concerned with the representation of data and control in terms of individual bits of storage and primitive machine instructions. The machine-language programmer is concerned with using the given hardware to erect systems and utilities for the efficient implementation of resource-limited computations. High-level languages, erected on a machine-language substrate, hide concerns about the representation of data as collections of bits and the representation of programs as sequences of primitive instructions. These languages have means of combination and abstraction, such as procedure definition, that are appropriate to the larger-scale organization of systems. 26 | 27 |
Metalinguistic abstraction – establishing new languages – plays an important role in all branches of engineering design. It is particularly important to computer programming, because in programming not only can we formulate new languages but we can also implement these languages by constructing evaluators. An evaluator (or interpreter) for a programming language is a procedure that, when applied to an expression of the language, performs the actions required to evaluate that expression. 28 | 29 |
It is no exaggeration to regard this as the most fundamental idea in programming: 30 | 31 |
The evaluator, which determines the meaning of expressions in a programming language, is just another program.32 | 33 |
To appreciate this point is to change our images of ourselves as programmers. We come to see ourselves as designers of languages, rather than only users of languages designed by others. 34 | 35 |
In fact, we can regard almost any program as the evaluator for some language. For instance, the polynomial manipulation system of section 2.5.3 embodies the rules of polynomial arithmetic and implements them in terms of operations on list-structured data. If we augment this system with procedures to read and print polynomial expressions, we have the core of a special-purpose language for dealing with problems in symbolic mathematics. The digital-logic simulator of section 3.3.4 and the constraint propagator of section 3.3.5 are legitimate languages in their own right, each with its own primitives, means of combination, and means of abstraction. Seen from this perspective, the technology for coping with large-scale computer systems merges with the technology for building new computer languages, and computer science itself becomes no more (and no less) than the discipline of constructing appropriate descriptive languages. 36 | 37 |
We now embark on a tour of the technology by which languages are established in terms of other languages. In this chapter we shall use Lisp as a base, implementing evaluators as Lisp procedures. Lisp is particularly well suited to this task, because of its ability to represent and manipulate symbolic expressions. We will take the first step in understanding how languages are implemented by building an evaluator for Lisp itself. The language implemented by our evaluator will be a subset of the Scheme dialect of Lisp that we use in this book. Although the evaluator described in this chapter is written for a particular dialect of Lisp, it contains the essential structure of an evaluator for any expression-oriented language designed for writing programs for a sequential machine. (In fact, most language processors contain, deep within them, a little ``Lisp'' evaluator.) The evaluator has been simplified for the purposes of illustration and discussion, and some features have been left out that would be important to include in a production-quality Lisp system. Nevertheless, this simple evaluator is adequate to execute most of the programs in this book.2 38 | 39 |
An important advantage of making the evaluator accessible as a Lisp program is that we can implement alternative evaluation rules by describing these as modifications to the evaluator program. One place where we can use this power to good effect is to gain extra control over the ways in which computational models embody the notion of time, which was so central to the discussion in chapter 3. There, we mitigated some of the complexities of state and assignment by using streams to decouple the representation of time in the world from time in the computer. Our stream programs, however, were sometimes cumbersome, because they were constrained by the applicative-order evaluation of Scheme. In section 4.2, we'll change the underlying language to provide for a more elegant approach, by modifying the evaluator to provide for normal-order evaluation. 40 | 41 |
Section 4.3 implements a more ambitious linguistic change, whereby expressions have many values, rather than just a single value. In this language of nondeterministic computing, it is natural to express processes that generate all possible values for expressions and then search for those values that satisfy certain constraints. In terms of models of computation and time, this is like having time branch into a set of ``possible futures'' and then searching for appropriate time lines. With our nondeterministic evaluator, keeping track of multiple values and performing searches are handled automatically by the underlying mechanism of the language. 42 | 43 |
In section 4.4 we implement a logic-programming language in which knowledge is expressed in terms of relations, rather than in terms of computations with inputs and outputs. Even though this makes the language drastically different from Lisp, or indeed from any conventional language, we will see that the logic-programming evaluator shares the essential structure of the Lisp evaluator. 44 | 45 | {{footnotes}} 46 | @@ 47 | -------------------------------------------------------------------------------- /content/4-2-lazy.content.html: -------------------------------------------------------------------------------- 1 | {{title}} 2 |
Now that we have an evaluator expressed as a Lisp program, we can experiment with alternative choices in language design simply by modifying the evaluator. Indeed, new languages are often invented by first writing an evaluator that embeds the new language within an existing high-level language. For example, if we wish to discuss some aspect of a proposed modification to Lisp with another member of the Lisp community, we can supply an evaluator that embodies the change. The recipient can then experiment with the new evaluator and send back comments as further modifications. Not only does the high-level implementation base make it easier to test and debug the evaluator; in addition, the embedding enables the designer to snarf@footnote{Snarf: ``To grab, especially a large document or file for the purpose of using it either with or without the owner's permission.'' Snarf down: ``To snarf, sometimes with the connotation of absorbing, processing, or understanding.'' (These definitions were snarfed from Steele et al. 1983. See also Raymond 1993.)} features from the underlying language, just as our embedded Lisp evaluator uses primitives and control structure from the underlying Lisp. Only later (if ever) need the designer go to the trouble of building a complete implementation in a low-level language or in hardware. In this section and the next we explore some variations on Scheme that provide significant additional expressive power. 16 | 17 | @menu 18 | * 4-2-1:: Normal Order and Applicative Order 19 | * 4-2-2:: An Interpreter with Lazy Evaluation 20 | * 4-2-3:: Streams as Lazy Lists 21 | @end menu 22 | 23 | 24 |
In section 1.1, where we began our discussion of models of evaluation, we noted that Scheme is an applicative-order language, namely, that all the arguments to Scheme procedures are evaluated when the procedure is applied. In contrast, normal-order languages delay evaluation of procedure arguments until the actual argument values are needed. Delaying evaluation of procedure arguments until the last possible moment (e.g., until they are required by a primitive operation) is called lazy evaluation .@footnote{The difference between the ``lazy'' terminology and the ``normal-order'' terminology is somewhat fuzzy. Generally, ``lazy'' refers to the mechanisms of particular evaluators, while ``normal-order'' refers to the semantics of languages, independent of any particular evaluation strategy. But this is not a hard-and-fast distinction, and the two terminologies are often used interchangeably.} Consider the procedure 27 | 28 |
Evaluating (try 0 (/ 1 0)) generates an error in Scheme. With lazy evaluation, there would be no error. Evaluating the expression would return 1, because the argument (/ 1 0) would never be evaluated. 37 | 38 |
An example that exploits lazy evaluation is the definition of a procedure unless 39 | 40 |
that can be used in expressions such as 50 | 51 |
This won't work in an applicative-order language because both the usual value and the exceptional value will be evaluated before unless is called (compare Exercise 1-6). An advantage of lazy evaluation is that some procedures, such as unless, can do useful computation even if evaluation of some of their arguments would produce errors or would not terminate. 62 | 63 |
If the body of a procedure is entered before an argument has been evaluated we say that the procedure is non-strict in that argument. If the argument is evaluated before the body of the procedure is entered we say that the procedure is strict in that argument.@footnote{The ``strict''versus ``non-strict'' terminology means essentially the same thing as ``applicative-order'' versus ``normal-order,'' except that it refers to individual procedures and arguments rather than to the language as a whole. At a conference on programming languages you might hear someone say, ``The normal-order language Hassle has certain strict primitives. Other procedures take their arguments by lazy evaluation.''} In a purely applicative-order language, all procedures are strict in each argument. In a purely normal-order language, all compound procedures are non-strict in each argument, and primitive procedures may be either strict or non-strict. There are also languages (see Exercise 4-31) that give programmers detailed control over the strictness of the procedures they define. 64 | 65 |
A striking example of a procedure that can usefully be made non-strict is cons (or, in general, almost any constructor for data structures). One can do useful computation, combining elements to form data structures and operating on the resulting data structures, even if the values of the elements are not known. It makes perfect sense, for instance, to compute the length of a list without knowing the values of the individual elements in the list. We will exploit this idea in section 4.2.3 to implement the streams of Chapter 3 as lists formed of non-strict cons pairs. 66 | 67 |
Exercise 4.25: Suppose that (in ordinary applicative-order Scheme) we define unless as shown above and then define factorial in terms of unless as 69 | 70 |
What happens if we attempt to evaluate (factorial 5)? Will our definitions work in a normal-order language? 81 |
Exercise 4.26: Ben Bitdiddle and Alyssa P. Hacker disagree over the importance of lazy evaluation for implementing things such as unless. Ben points out that it's possible to implement unless in applicative order as a special form. Alyssa counters that, if one did that, unless would be merely syntax, not a procedure that could be used in conjunction with higher-order procedures. Fill in the details on both sides of the argument. Show how to implement unless as a derived expression (like cond or let), and give an example of a situation where it might be useful to have unless available as a procedure, rather than as a special form. 85 |
In this section we will implement a normal-order language that is the same as Scheme except that compound procedures are non-strict in each argument. Primitive procedures will still be strict. It is not difficult to modify the evaluator of section 4.1.1 so that the language it interprets behaves this way. Almost all the required changes center around procedure application. 91 | 92 |
The basic idea is that, when applying a procedure, the interpreter must determine which arguments are to be evaluated and which are to be delayed. The delayed arguments are not evaluated; instead, they are transformed into objects called thunks .@footnote{The word thunk was invented by an informal working group that was discussing the implementation of call-by-name in Algol 60. They observed that most of the analysis of (``thinking about'') the expression could be done at compile time; thus, at run time, the expression would already have been ``thunk'' about (Ingerman et al. 1960).} The thunk must contain the information required to produce the value of the argument when it is needed, as if it had been evaluated at the time of the application. Thus, the thunk must contain the argument expression and the environment in which the procedure application is being evaluated. 93 | 94 |
The process of evaluating the expression in a thunk is called forcing .@footnote{This is analogous to the use of force on the delayed objects that were introduced in Chapter 3 to represent streams. The critical difference between what we are doing here and what we did in Chapter 3 is that we are building delaying and forcing into the evaluator, and thus making this uniform and automatic throughout the language.} In general, a thunk will be forced only when its value is needed: when it is passed to a primitive procedure that will use the value of the thunk; when it is the value of a predicate of a conditional; and when it is the value of an operator that is about to be applied as a procedure. One design choice we have available is whether or not to memoize thunks, as we did with delayed objects in section 3.5.1. With memoization, the first time a thunk is forced, it stores the value that is computed. Subsequent forcings simply return the stored value without repeating the computation. We'll make our interpreter memoize, because this is more efficient for many applications. There are tricky considerations here, however.@footnote{Lazy evaluation combined with memoization is sometimes referred to as call-by-need 95 | 96 |
argument passing, in contrast to call-by-name argument passing. (Call-by-name, introduced in Algol 60, is similar to non-memoized lazy evaluation.) As language designers, we can build our evaluator to memoize, not to memoize, or leave this an option for programmers (Exercise 4-31). As you might expect from Chapter 3, these choices raise issues that become both subtle and confusing in the presence of assignments. (See Exercise 4-27 and Exercise 4-29.) An excellent article by Clinger (1982) attempts to clarify the multiple dimensions of confusion that arise here.} 97 | 98 |
The main difference between the lazy evaluator and the one in section 4.1 is in the handling of procedure applications in eval and apply. 101 | 102 |
The application? clause of eval becomes 103 | 104 |
This is almost the same as the application? clause of eval in section 4.1.1. For lazy evaluation, however, we call apply with the operand expressions, rather than the arguments produced by evaluating them. Since we will need the environment to construct thunks if the arguments are to be delayed, we must pass this as well. We still evaluate the operator, because apply needs the actual procedure to be applied in order to dispatch on its type (primitive versus compound) and apply it. 115 | 116 |
Whenever we need the actual value of an expression, we use 117 | 118 |
instead of just eval, so that if the expression's value is a thunk, it will be forced. 127 | 128 |
Our new version of apply is also almost the same as the version in section 4.1.1. The difference is that eval has passed in unevaluated operand expressions: For primitive procedures (which are strict), we evaluate all the arguments before applying the primitive; for compound procedures (which are non-strict) we delay all the arguments before applying the procedure. 129 | 130 |
The procedures that process the arguments are just like list-of-values from section 4.1.1, except that list-of-delayed-args delays the arguments instead of evaluating them, and list-of-arg-values uses actual-value instead of eval: 152 | 153 |
The other place we must change the evaluator is in the handling of if, where we must use actual-value instead of eval to get the value of the predicate expression before testing whether it is true or false: 173 | 174 |
Finally, we must change the driver-loop procedure (section 4.1.4) to use actual-value instead of eval, so that if a delayed value is propagated back to the read-eval-print loop, it will be forced before being printed. We also change the prompts to indicate that this is the lazy evaluator: 185 | 186 |
With these changes made, we can start the evaluator and test it. The successful evaluation of the try expression discussed in section 4.2.1 indicates that the interpreter is performing lazy evaluation: 204 | 205 |
Our evaluator must arrange to create thunks when procedures are applied to arguments and to force these thunks later. A thunk must package an expression together with the environment, so that the argument can be produced later. To force the thunk, we simply extract the expression and environment from the thunk and evaluate the expression in the environment. We use actual-value rather than eval so that in case the value of the expression is itself a thunk, we will force that, and so on, until we reach something that is not a thunk: 228 | 229 |
One easy way to package an expression with an environment is to make a list containing the expression and the environment. Thus, we create a thunk as follows: 240 | 241 |
Actually, what we want for our interpreter is not quite this, but rather thunks that have been memoized. When a thunk is forced, we will turn it into an evaluated thunk by replacing the stored expression with its value and changing the thunk tag so that it can be recognized as already evaluated.@footnote{Notice that we also erase the env from the thunk once the expression's value has been computed. This makes no difference in the values returned by the interpreter. It does help save space, however, because removing the reference from the thunk to the env once it is no longer needed allows this structure to be garbage-collected and its space recycled, as we will discuss in section 5.3. 257 | 258 |
Similarly, we could have allowed unneeded environments in the memoized delayed objects of section 3.5.1 to be garbage-collected, by having memo-proc do something like (set! proc '()) to discard the procedure proc (which includes the environment in which the delay was evaluated) after storing its value.} 259 | 260 |
Notice that the same delay-it procedure works both with and without memoization. 284 | 285 |
Give the missing values in the following sequence of interactions, and explain your answers.@footnote{This exercise demonstrates that the interaction between lazy evaluation and side effects can be very confusing. This is just what you might expect from the discussion in Chapter 3.} 301 | 302 |
Exercise 4.28: Eval uses actual-value rather than eval to evaluate the operator before passing it to apply, in order to force the value of the operator. Give an example that demonstrates the need for this forcing. 327 | 328 | 329 |
Exercise 4.29: Exhibit a program that you would expect to run much more slowly without memoization than with memoization. Also, consider the following interaction, where the id procedure is defined as in Exercise 4-27 and count starts at 0: 330 | 331 |
Give the responses both when the evaluator memoizes and when it does not. 350 |
Exercise 4.30: Cy D. Fect, a reformed C programmer, is worried that some side effects may never take place, because the lazy evaluator doesn't force the expressions in a sequence. Since the value of an expression in a sequence other than the last one is not used (the expression is there only for its effect, such as assigning to a variable or printing), there can be no subsequent use of this value (e.g., as an argument to a primitive procedure) that will cause it to be forced. Cy thus thinks that when evaluating sequences, we must force all expressions in the sequence except the final one. He proposes to modify eval-sequence from section 4.1.1 to use actual-value rather than eval: 354 | 355 |
Exercise 4.31: The approach taken in this section is somewhat unpleasant, because it makes an incompatible change to Scheme. It might be nicer to implement lazy evaluation as an upward-compatible extension , that is, so that ordinary Scheme programs will work as before. We can do this by extending the syntax of procedure declarations to let the user control whether or not arguments are to be delayed. While we're at it, we may as well also give the user the choice between delaying with and without memoization. For example, the definition 439 | 440 |
would define f to be a procedure of four arguments, where the first and third arguments are evaluated when the procedure is called, the second argument is delayed, and the fourth argument is both delayed and memoized. Thus, ordinary procedure definitions will produce the same behavior as ordinary Scheme, while adding the lazy-memo declaration to each parameter of every compound procedure will produce the behavior of the lazy evaluator defined in this section. Design and implement the changes required to produce such an extension to Scheme. You will have to implement new syntax procedures to handle the new syntax for define. You must also arrange for eval or apply to determine when arguments are to be delayed, and to force or delay arguments accordingly, and you must arrange for forcing to memoize or not, as appropriate. 449 |
In section 3.5.1, we showed how to implement streams as delayed lists. We introduced special forms delay and cons-stream, which allowed us to construct a ``promise'' to compute the cdr of a stream, without actually fulfilling that promise until later. We could use this general technique of introducing special forms whenever we need more control over the evaluation process, but this is awkward. For one thing, a special form is not a first-class object like a procedure, so we cannot use it together with higher-order procedures.@footnote{This is precisely the issue with the unless procedure, as in Exercise 4-26.} Additionally, we were forced to create streams as a new kind of data object similar but not identical to lists, and this required us to reimplement many ordinary list operations (map, append, and so on) for use with streams. 455 | 456 |
With lazy evaluation, streams and lists can be identical, so there is no need for special forms or for separate list and stream operations. All we need to do is to arrange matters so that cons is non-strict. One way to accomplish this is to extend the lazy evaluator to allow for non-strict primitives, and to implement cons as one of these. An easier way is to recall (section 2.1.3) that there is no fundamental need to implement cons as a primitive at all. Instead, we can represent pairs as procedures:@footnote{This is the procedural representation described in Exercise 2-4. Essentially any procedural representation (e.g., a message-passing implementation) would do as well. Notice that we can install these definitions in the lazy evaluator simply by typing them at the driver loop. If we had originally included cons, car, and cdr as primitives in the global environment, they will be redefined. (Also see Exercise 4-33 and Exercise 4-34.)} 457 | 458 |
In terms of these basic operations, the standard definitions of the list operations will work with infinite lists (streams) as well as finite ones, and the stream operations can be implemented as list operations. Here are some examples: 473 | 474 |
Note that these lazy lists are even lazier than the streams of Chapter 3: The car of the list, as well as the cdr, is delayed.@footnote{This permits us to create delayed versions of more general kinds of list structures, not just sequences. Hughes 1990 discusses some applications of ``lazy trees.''} In fact, even accessing the car or cdr of a lazy pair need not force the value of a list element. The value will be forced only when it is really needed -- e.g., for use as the argument of a primitive, or to be printed as an answer. 510 | 511 |
Lazy pairs also help with the problem that arose with streams in section 3.5.4, where we found that formulating stream models of systems with loops may require us to sprinkle our programs with explicit delay operations, beyond the ones supplied by cons-stream. With lazy evaluation, all arguments to procedures are delayed uniformly. For instance, we can implement procedures to integrate lists and solve differential equations as we originally intended in section 3.5.4: 512 | 513 |
Exercise 4.32: Give some examples that illustrate the difference between the streams of Chapter 3 and the ``lazier'' lazy lists described in this section. How can you take advantage of this extra laziness? 537 |
Exercise 4.33: Ben Bitdiddle tests the lazy list implementation given above by evaluating the expression 541 | 542 |
To his surprise, this produces an error. After some thought, he realizes that the ``lists'' obtained by reading in quoted expressions are different from the lists manipulated by the new definitions of cons, car, and cdr. Modify the evaluator's treatment of quoted expressions so that quoted lists typed at the driver loop will produce true lazy lists. 550 |
Exercise 4.34: Modify the driver loop for the evaluator so that lazy pairs and lists will print in some reasonable way. (What are you going to do about infinite lists?) You may also need to modify the representation of lazy pairs so that the evaluator can identify them in order to print them. 554 |
18 | 19 | My aim is to show that the heavenly machine is not a kind of divine, live being, but a kind of clockwork (and he who believes that a clock has soul attributes the maker's glory to the work), insofar as nearly all the manifold motions are caused by a most simple and material force, just as all motions of the clock are caused by a single weight. 20 | 21 |24 | 25 |Johannes Kepler (letter to Herwart von Hohenburg, 1605) 22 | 23 |
We began this book by studying processes and by describing processes in terms of procedures written in Lisp. To explain the meanings of these procedures, we used a succession of models of evaluation: the substitution model of chapter 1, the environment model of chapter 3, and the metacircular evaluator of chapter 4. Our examination of the metacircular evaluator, in particular, dispelled much of the mystery of how Lisp-like languages are interpreted. But even the metacircular evaluator leaves important questions unanswered, because it fails to elucidate the mechanisms of control in a Lisp system. For instance, the evaluator does not explain how the evaluation of a subexpression manages to return a value to the expression that uses this value, nor does the evaluator explain how some recursive procedures generate iterative processes (that is, are evaluated using constant space) whereas other recursive procedures generate recursive processes. These questions remain unanswered because the metacircular evaluator is itself a Lisp program and hence inherits the control structure of the underlying Lisp system. In order to provide a more complete description of the control structure of the Lisp evaluator, we must work at a more primitive level than Lisp itself. 26 | 27 |
In this chapter we will describe processes in terms of the step-by-step operation of a traditional computer. Such a computer, or register machine, sequentially executes instructions that manipulate the contents of a fixed set of storage elements called registers. A typical register-machine instruction applies a primitive operation to the contents of some registers and assigns the result to another register. Our descriptions of processes executed by register machines will look very much like ``machine-language'' programs for traditional computers. However, instead of focusing on the machine language of any particular computer, we will examine several Lisp procedures and design a specific register machine to execute each procedure. Thus, we will approach our task from the perspective of a hardware architect rather than that of a machine-language computer programmer. In designing register machines, we will develop mechanisms for implementing important programming constructs such as recursion. We will also present a language for describing designs for register machines. In section 5.2 we will implement a Lisp program that uses these descriptions to simulate the machines we design. 28 | 29 |
Most of the primitive operations of our register machines are very simple. For example, an operation might add the numbers fetched from two registers, producing a result to be stored into a third register. Such an operation can be performed by easily described hardware. In order to deal with list structure, however, we will also use the memory operations car, cdr, and cons, which require an elaborate storage-allocation mechanism. In section 5.3 we study their implementation in terms of more elementary operations. 30 | 31 |
In section 5.4, after we have accumulated experience formulating simple procedures as register machines, we will design a machine that carries out the algorithm described by the metacircular evaluator of section 4.1. This will fill in the gap in our understanding of how Scheme expressions are interpreted, by providing an explicit model for the mechanisms of control in the evaluator. In section 5.5 we will study a simple compiler that translates Scheme programs into sequences of instructions that can be executed directly with the registers and operations of the evaluator register machine. 32 | 33 | {{footnotes}} 34 | @@ 35 | -------------------------------------------------------------------------------- /css/default.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* general */ 4 | 5 | * 6 | { 7 | background-color: inherit; 8 | color: inherit; 9 | line-height: 1.3; 10 | } 11 | 12 | .toggle-link { 13 | height:3em; 14 | } 15 | .toggle-link:hover { 16 | cursor:pointer; 17 | } 18 | 19 | .code-container { 20 | display:none; 21 | overflow:hidden; 22 | } 23 | 24 | ol li { 25 | margin-top: 0.8em; 26 | } 27 | 28 | tt { 29 | border: 1px #999999 solid; 30 | background-color: #eeeeee; 31 | } 32 | 33 | h3 { 34 | font-weight: bold; 35 | } 36 | 37 | h2 { 38 | margin-top:0.5em; 39 | } 40 | 41 | h1, h2, h3 { 42 | font-weight: normal; 43 | } 44 | 45 | hr { 46 | width: 50%; 47 | } 48 | 49 | code 50 | { 51 | display: block; 52 | font-family: monospace ! important; 53 | border: 1px #999999 solid; 54 | background-color: #eeeeee; 55 | padding: 0.2em; 56 | } 57 | 58 | #article 59 | { 60 | clear: both; 61 | background-color: transparent; 62 | min-height: 600px; 63 | } 64 | 65 | address 66 | { 67 | text-align: right; 68 | font-size: 75%; 69 | } 70 | 71 | #footer 72 | { 73 | margin: 0.5em 0; 74 | border: solid #404040 1px; 75 | border-style: solid none none none; 76 | } 77 | 78 | #footer * 79 | { 80 | margin: 0.5em 0; 81 | } 82 | 83 | #footer address 84 | { 85 | font-style: italic !important; 86 | } 87 | -------------------------------------------------------------------------------- /css/footnotes.css: -------------------------------------------------------------------------------- 1 | /*hack to hide the autogened footnotes 2 | div.footnote { 3 | display:none; 4 | } 5 | */ 6 | 7 | a.footnote_link, a.footnote_backlink { 8 | color: #841; 9 | text-decoration: none; 10 | /* cursor: default; */ 11 | font-size: 80%; 12 | vertical-align: top; 13 | position: relative; 14 | top: -0.4ex; 15 | margin:-1.5ex; 16 | padding:1.5ex; 17 | } 18 | 19 | a.footnote_link:hover, a.footnote_backlink:hover { 20 | color: #481; 21 | } 22 | 23 | #footnote_popup { 24 | font-family: Georgia; 25 | font-size: 100%; 26 | line-height: 1.5em; 27 | text-align: justify; 28 | background: #f0f0f0; 29 | color: #333; 30 | padding: 0.8ex; 31 | padding-left: 3ex; 32 | padding-right: 3ex; 33 | width: 54%; 34 | box-shadow: 4px 4px 22px #777; 35 | z-index: 12; 36 | } 37 | 38 | #footnote_popup a.footnote_backlink { 39 | display: none; 40 | } 41 | /* 42 | .footnote a { 43 | color:#841; 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /css/isicp.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 1em; 3 | } 4 | 5 | /*english text, meant for reading*/ 6 | p, table, label, h1, h2, h3, h4, a { 7 | font-family:Georgia; 8 | color:#2E2E2E; 9 | line-height:1.5em; 10 | text-align: justify; 11 | } 12 | 13 | p { 14 | font-size: 1.2em; 15 | } 16 | 17 | h1, h2, h3, h4 { 18 | margin-top: 1.5em; 19 | color: #444; 20 | } 21 | 22 | h2 { 23 | margin-top: 2em; 24 | } 25 | 26 | tt { 27 | border: 1px #999999 solid; 28 | padding-left:1px; 29 | padding-right:1px; 30 | background-color: rgba(0,0,0,0.05); 31 | } 32 | 33 | li { 34 | font-family:Georgia; 35 | color:#2E2E2E; 36 | } 37 | 38 | .figure { 39 | text-align: center; 40 | } 41 | 42 | .figure p { 43 | text-align: center; 44 | } 45 | 46 | .output_displayed_text { 47 | color: blue; 48 | } 49 | 50 | .output_error { 51 | color: red; 52 | } 53 | 54 | .p-link { 55 | text-decoration: underline; 56 | text-align: right; 57 | } 58 | 59 | .p-link:hover { 60 | cursor:pointer; 61 | } 62 | 63 | /*Suppress line break when beginning exercise*/ 64 | .exercise p:first-child { 65 | margin-top:0; 66 | } 67 | 68 | .output { 69 | font-family: monospace; 70 | font-style: italic; 71 | padding: 0pt 4pt; 72 | color: #444; 73 | } 74 | 75 | .exercise { 76 | background-color: #D8E5E8; 77 | padding: 1.5em; 78 | box-shadow: 0px 4px 5px lightgray; 79 | } 80 | 81 | /*ticks and crosses*/ 82 | 83 | .wrong-answer { 84 | margin-top: -1.5em; 85 | font-family: bold; 86 | color: red; 87 | float:right; 88 | } 89 | 90 | .right-answer { 91 | margin-top: -1.5em; 92 | font-family: bold; 93 | color: green; 94 | float:right; 95 | } 96 | 97 | .submit-ans { 98 | margin-top: 0em; 99 | } 100 | 101 | /*the tab that comes out on click*/ 102 | 103 | #sidebox { 104 | position:fixed; 105 | right:-30%; 106 | top:10px; 107 | width:35%; 108 | height:100%; 109 | } 110 | 111 | #sidebox .content { 112 | background:#bada55; 113 | } 114 | 115 | #sidebox .content { 116 | float:right; 117 | width:92%; 118 | height:100%; 119 | } 120 | 121 | #sidebox .tab { 122 | background:#bada55; 123 | cursor:pointer; 124 | float:left; 125 | width:8%; 126 | height:100%; 127 | } 128 | 129 | #sidebox .navlink { 130 | text-decoration: none; 131 | font-size: 2em; 132 | margin:0.1em; 133 | } 134 | 135 | #sidebox .navlink:hover { 136 | cursor: pointer; 137 | } 138 | 139 | #main { 140 | width: 45%; 141 | min-width: 400px; 142 | margin: auto; 143 | margin-bottom: 3em; 144 | } 145 | 146 | /*autoscroll*/ 147 | .CodeMirror-scroll { 148 | height: auto; 149 | width: auto; 150 | overflow-y: hidden; 151 | overflow-x: auto; 152 | } 153 | 154 | /*identifying editable portions*/ 155 | 156 | .CodeMirror-scroll:hover:not(.static) { 157 | background: rgba(0, 0, 0, 0.05); 158 | } 159 | 160 | .CodeMirror { 161 | margin-bottom: 0.5em; 162 | } 163 | 164 | /**/ 165 | 166 | div.CodeMirror span.CodeMirror-matchingbracket { 167 | text-decoration: none; 168 | font-weight: bolder; 169 | color: #787860; 170 | } 171 | 172 | #toc .h3 { 173 | font-size:14px; 174 | } 175 | #toc .h4, .div{ 176 | margin-left: 2em; 177 | font-size:12px; 178 | } 179 | 180 | label { /*thx cousera*/ 181 | padding-bottom:5px; 182 | float: none; 183 | margin-left: 5px; 184 | display: inline-block; 185 | vertical-align: middle; 186 | text-align: left; 187 | width: auto; 188 | max-width: 95%; 189 | } 190 | 191 | img.center { 192 | display:block; 193 | margin-left:auto; 194 | margin-right:auto; 195 | } 196 | 197 | .CodeMirror { 198 | line-height: 1.3em; 199 | } 200 | 201 | .CodeMirror-scrollbar { 202 | display: none !important; 203 | } 204 | -------------------------------------------------------------------------------- /css/lambda.css: -------------------------------------------------------------------------------- 1 | .console 2 | { 3 | height: 8em; 4 | overflow-x: hidden; 5 | overflow-y: auto; 6 | white-space: nowrap; 7 | border-style: solid; 8 | border-width: 4px; 9 | border-color: #333333; 10 | background-color: #000000; 11 | color: #ffffff; 12 | font-family: monospace; 13 | } 14 | 15 | #console 16 | { 17 | height: 20em; 18 | } 19 | 20 | .console ul 21 | { 22 | list-style-type: none; 23 | margin: 0; 24 | padding: 0 .5em; 25 | } 26 | 27 | .console ul li 28 | { 29 | margin-left: 0; 30 | } 31 | 32 | .console input 33 | { 34 | width: 100%; 35 | display: inline; 36 | border-style: none; 37 | background-color: rgba(0,0,0,0); 38 | ime-mode: disabled; 39 | } 40 | 41 | .console input:focus { 42 | outline:none; 43 | } 44 | 45 | .console ul li.userinput 46 | { 47 | font-weight: bold; 48 | } 49 | 50 | .console ul li.error 51 | { 52 | color: #ff0000; 53 | } 54 | 55 | .console .redex > .abstraction > .lambda 56 | { 57 | color: #8c8ce8; 58 | } 59 | 60 | .console .marked > .abstraction > .lambda, 61 | .console .marked > .abstraction > .binding, 62 | .console .marked .bound, 63 | .console .redex:hover > .abstraction > .lambda, 64 | .console .redex:hover > .abstraction > .binding 65 | { 66 | color: #00cccc; 67 | } 68 | 69 | .console .marked > .argument, 70 | .console .redex:hover > .argument 71 | { 72 | border-bottom-style: solid; 73 | border-bottom-width: 1px; 74 | border-bottom-color: #ffffff; 75 | } 76 | 77 | .console .redex 78 | { 79 | text-decoration: none; 80 | } 81 | 82 | .console .redex:hover 83 | { 84 | cursor: pointer; 85 | outline-style: solid; 86 | outline-width: 1px; 87 | outline-color: #6666ff; 88 | background-color: #333366; 89 | } 90 | 91 | .console .redex:active, 92 | .console .redex:active:hover 93 | { 94 | outline-style: solid; 95 | outline-width: 1px; 96 | outline-color: #ff3333 ! important; 97 | background-color: #663333 ! important; 98 | } 99 | 100 | .console .shadowed:hover, 101 | .console .shadowed:active, 102 | .console .shadowed:active:hover 103 | { 104 | outline-style: inherit ! important; 105 | outline-width: inherit ! important; 106 | outline-color: inherit ! important; 107 | background-color: inherit ! important; 108 | } 109 | 110 | .console .shadowed:hover > .abstraction > .lambda 111 | { 112 | color: #8c8ce8; 113 | } 114 | 115 | .console .shadowed:hover > .abstraction > .binding 116 | { 117 | color: inherit ! important; 118 | } 119 | 120 | .console .shadowed:hover > .argument 121 | { 122 | border-bottom-style: none ! important; 123 | } 124 | 125 | .console .reduce 126 | { 127 | margin-right: .5em; 128 | } 129 | 130 | .prompt 131 | { 132 | color: #999999; 133 | font-weight: bold; 134 | margin-right: .5em; 135 | } 136 | 137 | .abort 138 | { 139 | cursor: pointer; 140 | padding: .3em; 141 | margin: 4px 0 0 0; 142 | border: solid 4px #333333; 143 | background-color: #444444; 144 | color: #ffffff; 145 | -moz-border-radius: 4px; 146 | -webkit-border-radius: 4px; 147 | text-decoration: none; 148 | font-weight: bold; 149 | } 150 | 151 | .abort:hover 152 | { 153 | outline-style: none; 154 | border-color: #666666; 155 | background-color: #999999; 156 | } 157 | 158 | .abort:active, 159 | .abort:active:hover 160 | { 161 | outline-style: none; 162 | border-color: #ffcccc; 163 | } 164 | 165 | .abort .icon 166 | { 167 | padding: .1em .3em; 168 | margin-right: .3em; 169 | vertical-align: middle; 170 | text-align: center; 171 | background-color: #990000; 172 | -moz-border-radius: 4px; 173 | -webkit-border-radius: 4px; 174 | } 175 | 176 | .resize-corner 177 | { 178 | color: #999999; 179 | padding: 0 4px 4px 0; 180 | } 181 | 182 | #store 183 | { 184 | display: none; 185 | } 186 | 187 | ul.select 188 | { 189 | list-style-type: none; 190 | height: 1.3em; 191 | margin: .5em 0 0 0; 192 | padding: 0; 193 | } 194 | 195 | ul.select li 196 | { 197 | float: left; 198 | margin: 0 .5em 0 0; 199 | } 200 | 201 | ul.select li.label 202 | { 203 | font-weight: bold; 204 | } 205 | 206 | ul.select li a 207 | { 208 | cursor: pointer; 209 | margin: 0; 210 | padding: 0 .3em; 211 | font-weight: bold; 212 | text-decoration: none; 213 | color: #999999; 214 | background-color: #eeeeee; 215 | -moz-border-radius: .3em; 216 | -webkit-border-radius: .3em; 217 | } 218 | 219 | ul.select li a:hover 220 | { 221 | color: #9999ee; 222 | background-color: #e0e0f0; 223 | border-style: none; 224 | outline-style: none; 225 | text-decoration: underline; 226 | } 227 | 228 | ul.select li a:active, 229 | ul.select li a:active:hover 230 | { 231 | color: red; 232 | border-style: none; 233 | outline-style: none; 234 | } 235 | 236 | ul.select li.selected a, 237 | ul.select li.selected a:link:hover, 238 | ul.select li.selected a:visited:hover 239 | { 240 | color: #ffffff; 241 | background-color: #666666; 242 | text-decoration: none; 243 | border: solid 2px #999999; 244 | } 245 | 246 | #wait 247 | { 248 | border: solid 2px #666666; 249 | width: 3em; 250 | text-align: right; 251 | } 252 | 253 | #syntax th, #syntax td 254 | { 255 | padding: .5em; 256 | vertical-align: top; 257 | } 258 | 259 | #syntax th, #syntax th a 260 | { 261 | background-color: #999999; 262 | color: #ffffff; 263 | font-weight: bold; 264 | } 265 | 266 | #syntax td 267 | { 268 | background-color: #eeeeee; 269 | } 270 | 271 | #syntax dl 272 | { 273 | margin: 0; 274 | } 275 | 276 | #syntax code 277 | { 278 | display: inline; 279 | border-style: none; 280 | background-color: inherit; 281 | overflow: visible; 282 | } 283 | 284 | #syntax code var 285 | { 286 | font-style: italic; 287 | } 288 | 289 | #example dt 290 | { 291 | display: inline; 292 | padding: .1em .5em; 293 | font-weight: bold; 294 | color: black; 295 | background-color: #eeeeee; 296 | border-width: .15em; 297 | border-color: #666666; 298 | border-style: dashed none dashed none; 299 | } 300 | -------------------------------------------------------------------------------- /css/theme.css: -------------------------------------------------------------------------------- 1 | /* Default theme domesticated */ 2 | 3 | .cm-s-default span.cm-number {color: #481;} /* was #164 */ 4 | .cm-s-default span.cm-variable {color: #333;} /* black */ 5 | .cm-s-default span.cm-property {color: #333;} /* black */ 6 | .cm-s-default span.cm-operator {color: #333;} /* black */ 7 | .cm-s-default span.cm-builtin {color: #148;} /* #30a */ 8 | 9 | -------------------------------------------------------------------------------- /images/book-Z-G-D-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/book-Z-G-D-16.gif -------------------------------------------------------------------------------- /images/ch1-Z-G-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch1-Z-G-1.gif -------------------------------------------------------------------------------- /images/ch1-Z-G-10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch1-Z-G-10.gif -------------------------------------------------------------------------------- /images/ch1-Z-G-13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch1-Z-G-13.gif -------------------------------------------------------------------------------- /images/ch1-Z-G-6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch1-Z-G-6.gif -------------------------------------------------------------------------------- /images/ch1-Z-G-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch1-Z-G-7.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-11.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-12.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-13.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-15.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-16.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-17.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-23.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-24.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-25.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-26.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-27.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-28.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-29.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-30.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-31.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-32.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-33.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-34.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-35.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-36.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-37.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-38.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-39.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-40.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-41.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-42.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-51.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-52.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-53.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-54.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-59.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-6.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-62.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-63.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-64.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-65.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-66.gif -------------------------------------------------------------------------------- /images/ch2-Z-G-67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch2-Z-G-67.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-1.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-10.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-11.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-12.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-13.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-14.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-15.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-16.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-17.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-18.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-19.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-2.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-20.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-21.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-22.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-23.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-24.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-25.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-26.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-3.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-30.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-4.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-5.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-6.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-7.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-8.gif -------------------------------------------------------------------------------- /images/ch3-Z-G-9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch3-Z-G-9.gif -------------------------------------------------------------------------------- /images/ch4-Z-G-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/ch4-Z-G-1.gif -------------------------------------------------------------------------------- /images/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /images/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /images/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /images/envelope.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 60 | -------------------------------------------------------------------------------- /images/file-edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 98 | -------------------------------------------------------------------------------- /images/film.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 92 | -------------------------------------------------------------------------------- /images/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 98 | -------------------------------------------------------------------------------- /images/icons.source.md: -------------------------------------------------------------------------------- 1 | elusive iconfonts for bootstrap -------------------------------------------------------------------------------- /images/list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 97 | -------------------------------------------------------------------------------- /images/photo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/screenshot.png -------------------------------------------------------------------------------- /images/video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 93 | -------------------------------------------------------------------------------- /images/zoom-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldct/isicp/136a9ecc1cefa9e77140fdd66e06f40176dcb216/images/zoom-screenshot.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 |Wizard Book n. Structure and Interpretation of Computer Programs, an excellent computer science text used in introductory courses at MIT. So called because of the wizard on the jacket. One of the bibles of the LISP/Scheme world. 39 |40 | 41 |
Interactive SICP n. Interactive Structure and Interpretation of Computer Programs. Online version of SICP with a built-in scheme interpreter to allow readers to edit and run the code embedded in SICP. (Work in progress) 42 |43 | 44 | 45 |
63 |
64 | We'll begin our introduction to the lambda calculus by considering the question of how we might write down a function. There are standard ways to notate numbers,
72 |
73 | $$
74 | \def\.{\kern0pt{.}}
75 | 42 \\
76 | 0x2a
77 | $$
78 |
79 | sets,
80 |
81 | $$
82 | \{0,1,2\} \\
83 | \{x | x > 0, x \in R\}
84 | $$
85 |
86 | etc, but there doesn't seem to be a way to write down functions. How should we write down a function, for example, that takes a number and triples it?
91 |
92 | "Easy!" you say. "Just call that function $f$ and write
93 |
94 | $$
95 | f(x) = 3x
96 | $$
97 |
98 |
99 |
100 | to define it."
101 |
102 | Well, this approach suffers the drawback that you must give the function a name. I want a way to write down anonymous functions - functions that do not have a name. I want to be able to say
103 |
104 | $$
105 | f = something
106 | $$
107 |
108 | This is indispensible in writing higher-order functions, which are functions that return other functions. In the lambda calculus, we would write $f$ as
109 |
110 | $$
111 | \lambda x.3x
112 | $$
113 |
114 | Where, in general, a function that takes an argument and returns something is written as
115 |
116 | $$
117 | \lambda [argument]\.[return\ value]
118 | $$
119 |
120 | By the way, function application will be denoted simply by writing the function next to its operand, like so
121 |
122 | $$
123 | fx
124 | $$
125 |
126 | Instead of using parentheses as in $f(x)$ because, as you will see later, there are more important things for parentheses to do.
127 |
128 | Let's now create a universe for our functions to live in. Mathematically inclined readers might now point out that this universe must be incomplete - after all, functions must act on something, so surely we must include something else in the universe for them to act on, for example integers.
131 |
132 | To get around this, we shall let functions act on other functions; our functions shall accept other functions as arguments and returns functions. This way, our universe will only contain functions. It might seem at first that there aren't any interesting functions to write down, but let's see how far we can get. First, a function that does absolutely nothing, the identity function:
133 |
134 | $$
135 | I = \lambda t\.t
136 | $$
137 |
138 | "You got lucky," I hear you say. "Your universe can only contain silly functions like these - I don't think you can come up with many more." Well, how about this?
139 |
140 | $$
141 | C_I = \lambda s\.(\lambda t\.t)
142 | $$
143 |
144 | No matter what you pass to it, this function $C_I$ will always return the identity function. Here we see clearly why lambda notation is superior; without it, we would have to do this to define $C_I$
145 |
146 | $$
147 | C_I s = I \text{ for all }s\\
148 | \text{where }It = t \text{ for all t}
149 | $$
150 |
151 | I hope you got the hint that $C_I$ is so named because it is the constant function of I; we can generalise to a whole class of constant functions of r
152 |
153 | $$
154 | C_r = \lambda s\.r
155 | $$
156 |
157 | heck, let's create a function that creates these $C_r$'s
158 |
159 | $$
160 | K = \lambda r\.(\lambda s\.r)
161 | $$
162 |
163 | with the property that $Kr = C_r$. The rabbit hole goes deeper and deeper.
164 |
165 |
Interactive SICP is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Based on a work at https://mitpress.mit.edu/sicp/.
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/js/TOC.js:
--------------------------------------------------------------------------------
1 | create_TOC = function () {
2 | $("h3, h4").each(function(i) {
3 |
4 | var current = $(this);
5 |
6 | var title = current.text().slice(0,50).replace(/^\s+/, "").replace(/\s+$/, "").replace(/:/, "").replace(/\s+/g, "-").replace(/\./g, "-").replace(/\-+/g, "-").replace(/[\(\)]/g, "").replace(/\?/, "").replace(/'/g, "");
7 |
8 | current.attr("id", title);
9 |
10 | var a = $("", {href: "#" + title, html: current.text().slice(0,50), 'class': current[0].nodeName.toLowerCase()});
11 |
12 | a.click(function() {
13 | $('html, body').animate({
14 | 'scrollTop': $('#' + title).offset().top
15 | }, 250);
16 | });
17 |
18 | $("#toc").append(a).append($('
'));
19 | });
20 |
21 | $('#sidebox').animate({'right':'0%'});
22 | };
23 |
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | if (typeof LambdaJS == 'undefined') var LambdaJS = {};
2 | if (typeof LambdaJS.App == 'undefined') LambdaJS.App = {};
3 |
4 | (function(ns) {
5 | ns.testJS18 = function() {
6 | return [
7 | '(function(x) x)(1)',
8 | 'let x = 1'
9 | ].every(function(t) {
10 | try {
11 | eval(t);
12 | return true;
13 | } catch (e) {
14 | return false;
15 | }
16 | });
17 | };
18 | ns.isJS18Enabled = function() {
19 | if (typeof ns._isJS18Enabled == 'undefined') {
20 | ns._isJS18Enabled = ns.testJS18();
21 | }
22 | return ns._isJS18Enabled;
23 | };
24 | ns.hideSyntax = function(table, hide) {
25 | var hideCols = function(table, i) {
26 | for (var j=0; j < table.rows.length; j++) {
27 | var row = table.rows[j];
28 | if (row) {
29 | var elm = row.cells[i];
30 | if (elm) elm.style.display = 'none';
31 | }
32 | }
33 | };
34 | var head = table.rows[0];
35 | if (!head) return;
36 | for (var i=0; i < head.cells.length; i++) {
37 | if (head.cells[i].className == hide) {
38 | hideCols(table, i);
39 | break;
40 | }
41 | }
42 | };
43 | ns.Repl = function(elm, cont) {
44 | var self = {
45 | getWait: function(){ return 500; },
46 | getStrategy: function() {
47 | return new LambdaJS.Strategy.Leftmost();
48 | },
49 | getPP: function() {
50 | return new LambdaJS.PP.Lambda();
51 | },
52 | env: new LambdaJS.Env(),
53 | destruct: function() {
54 | delete self.strategy; delete self.marker; delete self.exp;
55 | if (self.abort) self.abort.die();
56 | },
57 | contDefault: function() {
58 | self.console.prompt();
59 | self.destruct();
60 | },
61 | parseDefault: function(cmd){ return self.env.evalLine(cmd); }
62 | };
63 | self.cont = cont || self.contDefault;
64 | self.parse = self.parseDefault;
65 | self.makeAbortButton = function() {
66 | var parent = self.console.enclosing;
67 | self.abort = new UI.AbortButton(parent, {
68 | position: 'absolute'
69 | }, function() {
70 | if (self.marker) {
71 | self.marker.setCallback(function(){});
72 | self.marker = null;
73 | }
74 | self.cont();
75 | });
76 | var btn = self.abort.button;
77 | var height = btn.offsetHeight;
78 | var val = function(p){ return parseInt(UI.getStyle(btn, p))||0; };
79 | height += val('borderTopWidth') || val('borderWidth');
80 | height += val('marginTop') || val('margin');
81 | btn.style.right = 0; btn.style.bottom = (-height)+'px';
82 | };
83 | self.console = new UI.Console(elm, function(cmd) {
84 | self.sandbox(function() {
85 | self.exp = self.parse(cmd);
86 | if (self.exp) {
87 | self.strategy = self.getStrategy();
88 | self.console.insert(self.getPP().pp(self.exp));
89 | self.makeAbortButton();
90 | self.mark();
91 | } else {
92 | self.cont();
93 | }
94 | return true;
95 | }, self.cont);
96 | }, null, function(evnt) {
97 | if ((evnt.charCode || evnt.keyCode) == 220) {
98 | UI.insertText(self.console.input, '\u03bb');
99 | return true;
100 | }
101 | return false;
102 | });
103 | self.sandbox = function(fun, cont) {
104 | try {
105 | if (fun()) return;
106 | } catch (e) {
107 | var meta = [];
108 | [ 'fileName', 'lineNumber' ].forEach(function(x) {
109 | if (/^([a-z]+)/.test(x) && e[x])
110 | meta.push(RegExp.$1 + ': ' + e[x]);
111 | });
112 | meta = meta.length ? ' ['+meta.join(', ')+']' : '';
113 | self.console.err(e.message + meta);
114 | }
115 | cont();
116 | };
117 | self.mark = function() {
118 | self.sandbox(function() {
119 | var strategy = self.getStrategy();
120 | self.exp = strategy.mark(self.exp);
121 | if (strategy.marked) {
122 | setTimeout(function() {
123 | if (self.abort()) return;
124 | self.marker = self.getPP();
125 | UI.replaceLastChild(self.console.view.lastChild,
126 | self.marker.pp(self.exp));
127 | self.reduce(self.marker);
128 | }, self.getWait());
129 | return true;
130 | }
131 | }, self.cont);
132 | };
133 | self.reduce = function(marker) {
134 | self.sandbox(function() {
135 | var strategy = self.getStrategy();
136 | self.exp = strategy.reduceMarked(self.exp);
137 | if (strategy.reduced) {
138 | var red = UI.$new('span', {
139 | klass: 'reduce',
140 | child: '\u2192'
141 | });
142 | setTimeout(function() {
143 | if (self.abort()) return;
144 | self.console.insert(red, self.marker.pp(self.exp));
145 | self.mark();
146 | }, self.getWait());
147 | } else {
148 | marker.setCallback(function(){ self.mark(); });
149 | }
150 | return true;
151 | }, self.cont);
152 | };
153 | self.cont();
154 | return self;
155 | };
156 | ns.StaticCode = function(decl) {
157 | var self = ns.StaticCode;
158 | self.hash = {};
159 | self.run = function(id) {
160 | var code = self.hash[id];
161 | if (code) {
162 | code.run();
163 | }
164 | };
165 | self.forEach = function(fun) {
166 | if (typeof fun == 'string') {
167 | var name = fun;
168 | fun = function(obj){ obj[name](); };
169 | }
170 | for (var id in self.hash) fun(self.hash[id]);
171 | };
172 | self.toLambda = function(){ self.forEach('toLambda'); };
173 | self.toJavaScript = function(){ self.forEach('toJavaScript'); };
174 | self.toJavaScript18 = function(){ self.forEach('toJavaScript18'); };
175 | var Code = function(node, decl) {
176 | var self = { node: node, code: node.textContent, decl: decl };
177 | if (typeof node.textContent == 'undefined') { // IE fix
178 | var s = ''; var len=node.childNodes.length; var child;
179 | for (var i=0; i < len && (child=node.childNodes[i]); i++) {
180 | if (child instanceof Text) s += child.toString();
181 | }
182 | self.code = s;
183 | }
184 | (node.className||'').split(/\s+/).forEach(function(name) {
185 | name = name.split('-').map(function(s) {
186 | return s.charAt(0).toUpperCase()+s.substring(1);
187 | }).join('');
188 | if (name in LambdaJS.Strategy) self.st = name;
189 | });
190 | var conv = function(pp, decl, code) {
191 | return code.split(/[\n\r]/).map(function(l) {
192 | var expr = l; var pre = ''; var post = '';
193 | if (/^(var|let)\s+([^\s=]+)\s*=\s*(.*)$/.test(expr)) {
194 | var d = RegExp.$1; var v = RegExp.$2; expr = RegExp.$3;
195 | pre = [ decl(d), v, '=', '' ].join(' ');
196 | }
197 | if (new RegExp('^([^;]*)(;.*)$').test(expr) ||
198 | new RegExp('^(.*?)( //.*)$').test(expr) ||
199 | new RegExp('^()(//.*)$').test(expr)) {
200 | expr = RegExp.$1; post = RegExp.$2;
201 | }
202 | var env = new LambdaJS.Env();
203 | try {
204 | if (expr.length > 0) {
205 | expr = env.evalLine(expr);
206 | expr = expr || { pp: function(){ return ''; } };
207 | expr = UI.text(pp.pp(expr));
208 | }
209 | return pre+expr+post;
210 | } catch (e) {
211 | return e.message;
212 | }
213 | }).join('\n');
214 | };
215 | self.t = function(what) {
216 | if (!self['code'+what]) {
217 | var pp = new LambdaJS.PP[what]();
218 | self['code'+what] = conv(pp, self.decl, self.code);
219 | }
220 | UI.removeAllChildren(self.node);
221 | self.node.appendChild(UI.$text(self['code'+what]));
222 | return self['code'+what];
223 | };
224 | self.toLambda = function(){ return self.t('Lambda'); };
225 | self.toJavaScript = function() {
226 | UI.removeAllChildren(self.node);
227 | self.node.appendChild(UI.$text(self.code));
228 | return self.code;
229 | };
230 | self.toJavaScript18 = function(){ return self.t('JavaScript18'); };
231 | self.run = function() {
232 | var parent = self.node.parentNode;
233 | console.log(parent);
234 | if (self.repl) {
235 | self.repl.abort.doAbort();
236 | parent = self.repl.console.enclosing;
237 | self.repl.console.destroy();
238 | parent.removeChild(UI.$('result-'+self.node.id));
239 | }
240 | var div = UI.$new('div', {
241 | klass: 'console', id: 'result-'+self.node.id
242 | });
243 | parent.appendChild(div);
244 | var repl = self.repl = new ns.Repl(div, function(){});
245 | var get = function(k){ return UI.$('input-'+k).value; };
246 | repl.getStrategy = function() {
247 | var st = self.st || get('strategy') || 'Leftmost';
248 | return new LambdaJS.Strategy[st];
249 | };
250 | repl.getPP = function() {
251 | return new LambdaJS.PP[get('pp') || 'JavaScript'];
252 | };
253 | repl.getWait = function() {
254 | var wait = get('wait');
255 | return (typeof wait != 'undefined') ? wait : 500;
256 | };
257 | repl.cont = function() {
258 | repl.destruct();
259 | repl.cont = repl.contDefault;
260 | repl.parse = repl.parseDefault;
261 | repl.console.prompt();
262 | };
263 | repl.parse = function(c){ return repl.env.evalLines(c); };
264 | repl.console.insert([
265 | '[', repl.getStrategy().name,
266 | '/', repl.getPP().name,
267 | ']' ].join(' '));
268 | repl.console.command(self.code);
269 | };
270 | return self;
271 | };
272 | var name = 'LambdaJS.App.StaticCode';
273 | var links = UI.doc.getElementsByTagName('a');
274 | for (var i=0; i < links.length; i++) {
275 | var node;
276 | if (links[i].id.match(/^run-(.+)/) && (node=UI.$(RegExp.$1))) {
277 | links[i].href = 'javascript:'+name+'.run(\''+node.id+'\')';
278 | self.hash[node.id] = new Code(node, decl);
279 | }
280 | }
281 | return self;
282 | };
283 | })(LambdaJS.App);
284 |
285 | function init(id) {
286 | with (LambdaJS.App) {
287 |
288 | // examples
289 | var declLet = function(){ return 'let'; };
290 | var declVar = function(){ return 'var'; };
291 | var exmpls = new StaticCode(isJS18Enabled() ? declLet : declVar);
292 |
293 | // REPL
294 | var elm = document.getElementById(id);
295 | var repl = new Repl(elm);
296 |
297 | var makeSelector = function(what, dflt, extra) {
298 | extra = extra || function(){};
299 | var lc = what.toLowerCase();
300 | var cat = LambdaJS[what]; var hash = {};
301 | for (var k in cat) hash[k] = { name: new cat[k]().name };
302 | new UI.Selector(lc, hash, function(key) {
303 | repl['get'+what] = function(){ return new cat[key]; };
304 | UI.$('input-'+lc).value = key;
305 | extra(key);
306 | if (repl.console.input) repl.console.input.focus();
307 | }, UI.$('input-'+lc).value || dflt);
308 | };
309 |
310 | // strategy
311 | makeSelector('Strategy', 'Leftmost');
312 |
313 | // output
314 | if (!isJS18Enabled()) delete LambdaJS.PP.JavaScript18;
315 | var lang = /\#js$/.test(location.href) ? 'JavaScript' : 'Lambda';
316 | makeSelector('PP', lang, function(key){ exmpls['to'+key](); });
317 |
318 | // wait
319 | var ul = UI.$('pp');
320 | ul.appendChild(UI.$new('li', { klass: 'label', child: 'Wait:' }));
321 | var input = UI.$new('input', { id: 'wait' });
322 | var sync = function(){ UI.$('input-wait').value = input.value; };
323 | new UI.Observer(input, 'onchange', sync);
324 | new UI.Observer(input, 'onkeyup', sync);
325 | var w = UI.$('input-wait').value;
326 | input.value = w.length ? w : 500;
327 | sync();
328 | ul.appendChild(input);
329 | repl.getWait = function(){ return input.value; };
330 | }
331 | };
332 |
--------------------------------------------------------------------------------
/js/footnotes.js:
--------------------------------------------------------------------------------
1 | // Shows footnotes as popups, requires jQuery.
2 | // Originally written by Lukas Mathis:
3 | // http://ignorethecode.net/blog/2010/04/20/footnotes/
4 | // Adapted by Andres Raba, public domain.
5 |
6 | $(document).ready(function() {
7 |
8 | // Configuration
9 | var doc = "html",
10 | popup = "#footnote_popup",
11 | old_doc_bg = $(doc).css("background"),
12 | old_popup_bg = $(popup).css("background"),
13 | new_doc_bg = "#e8e8e8",
14 | new_popup_bg = "#ffffff";
15 |
16 | var Footnotes = {
17 | footnotetimeout: false,
18 | setup: function() {
19 | var footnotelinks = $("a[class='footnote_link']");
20 |
21 | footnotelinks.unbind('mouseover',Footnotes.footnoteover);
22 | footnotelinks.unbind('mouseout',Footnotes.footnoteout);
23 |
24 | footnotelinks.bind('mouseover',Footnotes.footnoteover);
25 | footnotelinks.bind('mouseout',Footnotes.footnoteout);
26 | },
27 |
28 | footnoteover: function() {
29 | clearTimeout(Footnotes.footnotetimeout);
30 | $(popup).stop();
31 | $(popup).remove();
32 |
33 | var id = $(this).attr('href').substr(1);
34 | var position = $(this).offset();
35 |
36 | var div = $(document.createElement('div'));
37 | div.attr('id', popup.substr(1));
38 | div.bind('mouseover',Footnotes.divover);
39 | div.bind('mouseout',Footnotes.footnoteout);
40 |
41 | var el = document.getElementById(id);
42 | div.html($.parseHTML($(el).html()));
43 |
44 | var popup_width = $("#main").width() * 0.96;
45 | div.css({
46 | position:'absolute',
47 | width: popup_width,
48 | opacity:1
49 | });
50 |
51 | $(document.body).append(div);
52 |
53 | var left = $("#main").offset().left - 10;
54 |
55 | // Popup opens below the link unless there's
56 | // not enough room below and enough above.
57 | var top = position.top + 33;
58 | if ((top + div.height() + 18 >
59 | $(window).height() + $(window).scrollTop())
60 | &&
61 | (top - div.height() - 40 > $(window).scrollTop())) {
62 | top = position.top - div.height() - 5;
63 | }
64 | div.css({ left: left,
65 | top: top,
66 | background: new_popup_bg });
67 | },
68 |
69 | footnoteout: function() {
70 | Footnotes.footnotetimeout = setTimeout(function() {
71 | $(popup).animate({
72 | opacity: 0
73 | }, 600, function() {
74 | $(popup).remove();
75 | // $(doc).css({ background: old_doc_bg });
76 | $(popup).css({ background: old_popup_bg });
77 | });
78 | },400);
79 | },
80 |
81 | divover: function() {
82 | clearTimeout(Footnotes.footnotetimeout);
83 | $(popup).stop();
84 | $(popup).css({
85 | opacity: 1
86 | });
87 | // $(doc).css({ background: new_doc_bg });
88 | $(popup).css({ background: new_popup_bg });
89 | }
90 | }
91 |
92 | Footnotes.setup();
93 |
94 | });
95 |
--------------------------------------------------------------------------------
/js/lambda/lambda.js:
--------------------------------------------------------------------------------
1 | function format(s) {
2 | function helper(s, args) {
3 | if (args.length === 0) {
4 | return s;
5 | } else {
6 | return helper(s.replace("%s", args[0]), args.slice(1));
7 | }
8 | }
9 | return helper(s, Array.prototype.slice.call(arguments).slice(1));
10 | }
11 |
12 | /*
13 | Unbound variables ("constants") are represented as strings
14 | Bound variables are represented as numbers, interpreted as de brujin indices
15 | */
16 |
17 | function lambdaTerm(body) {
18 | this.body = body;
19 | }
20 |
21 | lambdaTerm.prototype.toString = function() {
22 | return format("(λ %s)", this.body.toString());
23 | }
24 |
25 | function applicationTerm(func, body) {
26 | this.func = func;
27 | this.body = body;
28 | }
29 |
30 | applicationTerm.prototype.toString = function() {
31 | return format("(%s %s)", this.func.toString(), this.body.toString());
32 | }
33 |
34 | function l(body) {
35 | return new lambdaTerm(body);
36 | }
37 |
38 | function a(func, body) {
39 | return new applicationTerm(func, body);
40 | }
41 |
42 | function lift(k, n, term) {
43 | if (typeof term === "number") {
44 | if (term < k) {
45 | return term;
46 | } else {
47 | return term + n
48 | }
49 | } else if (typeof term === "string") {
50 | return term;
51 | } else if (term instanceof applicationTerm) {
52 | return new applicationTerm(lift(k, n, term.func), (lift(k, n, term.body)));
53 | } else if (term instanceof lambdaTerm) {
54 | return new lambdaTerm(lift(k+1, n, term.body));
55 | } else {
56 | throw "lift called with invalid term " + term;
57 | }
58 | }
59 |
60 | function sub(term, k, N) {
61 | if (typeof term === "number") {
62 | if (term < k) {
63 | return term;
64 | } else if (term === k) {
65 | return lift(1, k-1, N);
66 | } else {
67 | return term - 1;
68 | }
69 | } else if (typeof term === "string") {
70 | return term;
71 | } else if (term instanceof lambdaTerm) {
72 | return new lambdaTerm(sub(term.body, k + 1, N));
73 | } else if (term instanceof applicationTerm) {
74 | return new applicationTerm(sub(term.func, k, N), sub(term.body, k, N));
75 | } else {
76 | throw "error: " + term + " matched no types";
77 | }
78 | }
79 |
80 | function apply_lambda(lambda_term, arg) {
81 | return sub(lambda_term.body, 1, arg);
82 | }
83 |
84 | function simplify(term) {
85 | if (typeof term === "number") {
86 | return term;
87 | } else if (typeof term === "string") {
88 | return term;
89 | } else if (term instanceof lambdaTerm) {
90 | return new lambdaTerm(simplify(term.body));
91 | } else if (term instanceof applicationTerm) {
92 | var newfunc = simplify(term.func);
93 | var newbody = simplify(term.body);
94 |
95 | if (newfunc instanceof lambdaTerm) {
96 | return apply_lambda(newfunc, newbody);
97 | } else {
98 | return new applicationTerm(newfunc, newbody);
99 | }
100 | } else {
101 | throw "error: " + term + " matched no types";
102 | }
103 | }
104 |
105 | function to_named_string(term) {
106 | function helper(term, fresh_vars) {
107 | if (fresh_vars.length == 0) {
108 | throw "run out of fresh variables";
109 | }
110 | if (typeof term === "number") {
111 | return String(term);
112 | } else if (typeof term === "string") {
113 | return term;
114 | } else if (term instanceof lambdaTerm) {
115 | var fresh = fresh_vars[0];
116 | return format("λ%s.%s", fresh, helper(apply_lambda(term, fresh), fresh_vars.slice(1)));
117 | } else if (term instanceof applicationTerm) {
118 | var new_func = helper(term.func, fresh_vars);
119 | var new_body = helper(term.body, fresh_vars)
120 |
121 | if (term.func instanceof lambdaTerm) {
122 | var left = format("(%s)", new_func);
123 | } else {
124 | var left = new_func;
125 | }
126 |
127 | if ((term.body instanceof applicationTerm) || (term.body instanceof lambdaTerm)) {
128 | var right = format("(%s)", new_body);
129 | } else {
130 | var right = new_body;
131 | }
132 |
133 | return left + right;
134 | } else {
135 | throw "error: " + term + " matched no types";
136 | }
137 | }
138 | return helper(term, ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"]).replace(/\.λ/g, "");
139 | }
140 |
--------------------------------------------------------------------------------
/js/lambda/test-lambda.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lambda.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 | From Church Numerals to Y Combinators
65 | An Interactive Tour of the Lambda Calculus
66 |
67 |
68 |
69 | Introduction: How to Write Functions
70 |
71 | Simple Beginnings
129 |
130 |
167 |
168 |
171 |
176 | var I = function(t){ return t };
172 | var C_I = function(s,t) { return t };
173 | var C_r = function(s) {return r};
174 | var K = function(r,s){ return r };
175 | Exercises
177 |
178 |
188 |
So far, all our functions have only accepted one argument - what happens if we want them to take two or more arguments? For example, think of the two-argument function $\zeta$ that applies its second argument to the first argument. 202 | 203 | $$ 204 | \zeta(x,y) = yx 205 | $$ 206 | 207 |
The way to write this is to make the function take only the first argument $x$ and return a partially evaluated function, which then takes the second argument and processes that, then returns the final result. 208 | 209 | $$ 210 | \zeta = \lambda x\.(\lambda y\.yx) 211 | $$ 212 | 213 |
As you can see, when we feed $\zeta$ the first argument... 214 | 215 | $$ 216 | \zeta x = \lambda y\.yx 217 | $$ 218 | 219 | we get a partially applied function. When we feed that the second argument... 220 | 221 | $$ 222 | (\zeta x)y = yx 223 | $$ 224 | 225 |
We get back the finished computation. This strategy is called currying and should be familiar to Haskell and ML programmers, among others. 226 | 227 |
You can see why we reserved brackets earlier on; without them, an expression like $KII$ is ambiguous because it could be read as either $(KI)I$ or $K(II)$. 230 | 231 | \begin{align} 232 | (KI)I &=C_I I \\ 233 | &= I \\ 234 | &= \lambda t\.t \\ 235 | K(II) &= KI \\ 236 | &= \lambda r\.I \\ 237 | &= \lambda r\.(\lambda t\.t) \\ 238 | \end{align} 239 | 240 |
Since the first one is much more common, we will often drop the brackets. Hence our convention is that when omitted, function application starts from the leftmost pair; $KII$ is read as $(KI)I$. 241 | 242 |
In a similar vein, let's adopt a convention and simplify this 243 | 244 | $$ 245 | \zeta = \lambda x\.(\lambda y\.yx) 246 | $$ 247 | 248 |
to this 249 | 250 | $$ 251 | \zeta = \lambda xy\.yx 252 | $$ 253 | 254 |
and say that multiple arguments appear between a $\lambda$ and a dot represent a curried function. Let's have a few more examples to make it clear. We'll introduce the projection operators $\pi_1$ and $\pi_2$, which are functions of two arguments that return either the first or the second argument. 255 | 256 | $$ 257 | \pi_1 = \lambda xy\.x \\ 258 | \pi_2 = \lambda xy\.y 259 | $$ 260 | 261 |
Notice how clearly we can see the definition now. Also notice that $\pi_1 = K$ and $\pi_2 = KI$. 262 | 263 |
269 | var zeta = function(x,y){ return y(x) };
270 | var pi_1 = function(x,y){return x};
271 | var pi_2 = function(x,y){return y};
272 |
273 | I promised in the subtitle that this would be an interactive tour. Click on the thin grey line above for the interactive part. Inside you will find the console to an interpreter as well as some pre-defined terms. Note that the interpreter considers KI to be one identifier and K I to be $K$ applied to $I$. Use \ to type $\lambda$. 299 | 300 |
By the way, there's another expandble section after the section Simple Beginnings. I suggest that you start with that one first. 301 | 302 |
There are many ways to represent numbers via functions, to say that this function represents one, that one represents two, etc. As an exercise, try thinking of one of them. 305 | 306 |
We'll focus on a classic encoding devised by Alonzo Church. The Church encoding of a number $n$ is a function that maps functions to their $n$-fold compositions. 307 | 308 | \begin{align} 309 | nf &= f^n \\ 310 | &= f \circ f \circ \ldots \circ f 311 | \end{align} 312 | 313 |
To use an example from trigonometry, 314 | 315 | \begin{align} 316 | 3 \sin &= \sin \circ \sin \circ \sin \\ 317 | (3 \sin) x &= sin(sin(sin(x))) 318 | \end{align} 319 | 320 |
To derive the explicit form for $n$, we first write $f^n$ explicitly as $\lambda x\. f(f(f\ldots f(x)\ldots))$. 321 | 322 | \begin{align} 323 | nf &= f^n \\ 324 | &= \lambda x \.f^n x \\ 325 | &= \lambda x \.f(f(f\ldots f(x)\ldots)) 326 | \end{align} 327 | 328 |
abstracting out the $f$, 329 | 330 | $$ 331 | n = \lambda fx \.f(f(f\ldots f(x)\ldots)) 332 | $$ 333 | 334 |
and here are some examples to illustrate. 335 | 336 | \begin{align} 337 | 0 &= \lambda fx\.x \\ 338 | 1 &= \lambda fx\.fx \\ 339 | 2 &= \lambda fx\.f(fx) \\ 340 | 3 &= \lambda fx\.f(f(fx)) 341 | \end{align} 342 | 343 |
Let's derive the successor function that adds one to a number. 344 | 345 | \begin{align} 346 | Sn &= \lambda fx\.f^{(n+1)}x \\ 347 | &= \lambda fx\.f(f^nx) \\ 348 | &= \lambda fx\.f(nfx) 349 | \end{align} 350 | 351 |
Hence, 352 | 353 | $$ 354 | S = \lambda nfx\.f(nfx) 355 | $$ 356 | 357 | 358 |
This relies on the fact that $f \circ f^n = f^{n+1}$. Using the general form $f^m \circ f^n = f^{m+n}$, we get a function $\Sigma$ that sums two numbers. 359 | 360 | $$ 361 | \Sigma = \lambda mnfx.mf(nfx) 362 | $$ 363 | 364 |
Let's do another derivation for practice – the operator $\Pi$ multiplies two numbers. We'll use the property $(f^m)^n = f^{mn}$ 365 | 366 | \begin{align} 367 | \Pi mn &= \lambda f\.f^{mn} \\ 368 | &= \lambda f\.(f^n)^m \\ 369 | &= \lambda f\.m(f^n) \\ 370 | &= \lambda f\.m(nf) \\ 371 | \Pi &= \lambda mnf\.m(nf) 372 | \end{align} 373 | 374 |
380 | var _0 = function(s,z){ return z };
381 | var _1 = function(s,z){ return s(z) };
382 | var _2 = function(s,z){ return s(s(z)) };
383 | var _3 = function(s,z){ return s(s(s(z))) };
384 |
385 | var successor = function(m,n,f,x) {return m(f)(n(f)(x))};
386 | var add = function(m,n,f,x){ return m(f)(n(f)(x)) };
387 |
388 | var multiply = function(m,n,f) { return m(n(f)) };
389 |
390 |
391 | Linked Lists, Stacks, Queues, Heaps, B-Trees – our universe is still missing such data structures or even a way to express them. Let's implement one of the building blocks of data structures, the pair. A pair is exactly what the name suggests it is – a container containing two elements in order, the head and the tail. Programmers using lisp-descended languages will know of them as cons cells. 424 | 425 |
Let's be more formal - we want a function $P$ that constructs a pair. Once the pair is constructed, functions $H$ and $T$ may be used to access the elements of the pair. Consistency requires that 426 | 427 | $$ 428 | H(Pxy) = x \\ 429 | T(Pxy) = y 430 | $$ 431 | 432 |
Here's a clever way of implementing $P$: 433 | 434 | $$ 435 | Pxy = \lambda t\.txy \\ 436 | $$ 437 | 438 |
This now allows us to pass in a function $t$ and returns $txy$. We are almost done; to get $x$, for example, we can just pass in $\pi_1$, the first projection operator 439 | 440 | \begin{align} 441 | (\lambda t\.txy)\pi_1 = \pi_1 xy = x 442 | \end{align} 443 | 444 |
Written out fully, 445 | 446 | \begin{align} 447 | P &= \lambda xyt\.txy \\ 448 | H &= \lambda p. p(\lambda xy\.x) \\ 449 | T &= \lambda p. p(\lambda xy\.y) 450 | \end{align} 451 | 452 | 453 |
459 | var pair = function(P,Q,x){ return x(P)(Q) };
460 | var fst = function(p){ return p(function(x,y){ return x }) };
461 | var snd = function(p){ return p(function(x,y){ return y }) };
462 | var p1 = pair(pair(a)(b))(pair(c)(d));
463 |
464 | There are many ways to embed booleans into the lambda calculus. We could even declare that the Church numeral $1$ represents $\text{True}$ and $0$ represents $\text{False}$. However, since booleans are normally used for control flow or corditional evaluation ("if statements"), we'll choose a representation that is most convenient for expressing such control flow. Hence if $b$ is a boolean we declare it to have the property that. 497 | 498 | $$ 499 | bxy 500 | $$ 501 | 502 |
evaluates to $x$ if b is true and $y$ otherwise. Then 503 | 504 | \begin{align} 505 | T &= \lambda ab.a \\ 506 | F &= \lambda ab.b 507 | \end{align} 508 | 509 |
We can also define the common logical manipulators 510 | 511 | \begin{align} 512 | NOT &= \lambda pab. pba\\ 513 | AND &= \lambda pq. pqF \\ 514 | OR &= \lambda pq. pTq 515 | \end{align} 516 | 517 |
for example (OR p q) = (p T q) which can be read as "T if p else q", which is a correct definition of $OR$. 518 | 519 |
Let's try writing a conditional expression that operates on Church numerals and tells us if its argument is zero. The trick is to make use of the fact that 520 | 521 | $$ 522 | C_r \circ C_r = C_r 523 | $$ 524 | 525 |
that is, constant functions don't change when they are composed with themselves. Similarly $C_r \circ C_r \circ C_r$ is still $C_r$, and so on. Hence, the expression 526 | 527 | $$ 528 | n C_F 529 | $$ 530 | 531 |
will evaluate to $C_F$ when $n > 0$. However, it will evaluate to $I$ when $n=0$; a zero-fold composition is the special case! Then, we simply need to pass $T$ to it 532 | 533 | $$ 534 | Zn = n(\lambda x\. F) T 535 | $$ 536 | 537 |
and we will get $T$ if $n=0$ and $F$ otherwise. Written out fully, 538 | 539 | $$ 540 | Z = \lambda n\. n(\lambda xab\. b) (\lambda ab\. b) 541 | $$ 542 | 543 |
549 | var T = function(x,y){ return x };
550 | var F = function(x,y){ return y };
551 |
552 | var not = function(p,a,b){return p(b)(a)};
553 | var and = function(p,q){return p(q)(function(x,y){return y})};
554 | var or = function(p,q){return p( function(x,y){ return x } )(q) };
555 |
556 | Let's try to define a factorial function that computen $n! = n(n-1)(n-2)...2\cdot 1$ 586 | 587 |
588 | fac = 589 | (lambda (n) 590 | (if (= n 0) 591 | 1 592 | (* n (fac (- n 1))))) 593 |594 | 595 |
There's a problem with this - lambda calculus does not allow recursive definitions. Remember, we want to define fac as an anonymous function, that is, without referring to fac. 596 | 597 |
Hence, the expression fac is not allowed within the definition. Let's replace it with something legal. 598 | 599 |
600 | F = 601 | (lambda (former-fac) 602 | (lambda (n) 603 | (if (= n 0) 604 | 1 605 | (* n (former-fac (- n 1)))))) 606 |607 | 608 |
you will notice that we have simply replaced fac by former-fac in the body and enclosed the whole thing in a (lambda (former-fac) .. ). We call the new function F 609 | 610 |
Let's imagine we already have a working copy of fac, perhaps from Plato's heaven. What is (F fac)? it is a function of one variable, 611 | 612 |
613 | (F fac) = 614 | (lambda (n) 615 | (if (= n 0) 616 | 1 617 | (* n (fac (- n 1))))) 618 |619 | 620 |
We see that (F fac) = fac; this means that fac is the fixed point of F. We are very close now, because F was defined without recursion. Now all we need is a function that finds the fixed point of its argument, a function $Y$ satisfying 621 | 622 | $$ 623 | F(YF) = YF 624 | $$ 625 | 626 |
The above equation expresses the fact that $YF$ is a fixed point of $F$. How can we define $Y$? 627 | 628 |
The solution to this is one of the most beautiful results of the Lambda Calculus. First, we must take a little detour. 631 | 632 |
Consider 633 | 634 | \begin{align} 635 | \omega = (\lambda x\. xx) (\lambda x\. xx) 636 | \end{align} 637 | 638 |
Realize that $\omega$ expands to itself, that is, substitutin the second term, $\lambda x\. xx$, into the $x$ of the first term simply makes the whole expression evaluate to $\omega$ again. With a small modification, 639 | 640 | \begin{align} 641 | \omega' &= (\lambda x\. F(xx)) (\lambda x\. F(xx)) \\ 642 | &= F (\lambda x\. F(xx)) (\lambda x\. F(xx)) \\ 643 | &= F(\omega') 644 | \end{align} 645 | 646 |
so $\omega'$ is a fixed point of $F$. Abstracting the $F$ out, we conclude that this is the Y Combinator. 647 | 648 | \begin{align} 649 | Y = \lambda F.(\lambda x. F(xx)) (\lambda x. F(xx)) 650 | \end{align} 651 | 652 |
658 | var Y = function(f){ return (function(x){ return f((x)(x)) })(function(x){ return f((x)(x)) }) };
659 |
660 | var _0 = function(s,z){ return z };
661 | var _1 = function(s,z){ return s(z) };
662 | var _2 = function(s,z){ return s(s(z)) };
663 | var _3 = function(s,z){ return s(s(s(z))) };
664 |
665 | var t = function(x,y){ return x }; // true
666 | var f = function(x,y){ return y }; // false
667 | var if0 = function(n){ return n(function(x){ return f })(t) }; // if n equals to _0
668 |
669 | var mul = function(m,n,s,z){ return n(m(s))(z) };
670 | var pred = function(n,s,z){ return n(function(f,g){ return g(f(s)) })(function(x){ return z })(function(x){ return x }) };
671 |
672 | var F = function(r,n){ return if0(n)(_1)(mul(n)(r(pred(n)))) };
673 | var fact = Y(F);
674 |
675 | Note: each letter $a$, $b$, $c$ etc correspond to a single variable. 686 |
If you enjoyed this, you may enjoy my work-in-progress Interactive Structure and Interpretation of Computer Programs. To get stay updated on future interactive guides, please enter your email below. 702 | 703 |
704 | 705 | 706 | 707 |
", r) 16 | r = re.sub("@end example", "", r) 17 | 18 | r = re.sub("@anchor{(.*?)}", "", r, flags=re.DOTALL) 19 | r = re.sub("@ref{(.*?)}", "\\1", r, flags=re.DOTALL) 20 | 21 | r = re.sub("@newterm{(.*?)}", " \\1 \n", r, flags=re.DOTALL) 22 | r = re.sub("@code{(.*?)}", " \\1 ", r, flags=re.DOTALL) 23 | r = re.sub("@var{(.*?)}", "\\1", r, flags=re.DOTALL) 24 | r = re.sub("@i{(.*?)}", "\\1", r, flags=re.DOTALL) 25 | r = re.sub("@emph{(.*?)}", "\\1", r, flags=re.DOTALL) 26 | 27 | r = re.sub("@r{(.*?)}", "\\1", r, flags=re.DOTALL) 28 | 29 | r = re.sub("@strong{(.*?)}", "\\1", r, flags=re.DOTALL) 30 | 31 | r = re.sub("@footnote{(.*?)}", "
Based on Structure and Interpretation of Computer Programs, a work at https://mitpress.mit.edu/sicp/.
102 |
103 |