├── .DS_Store ├── 01-building-abstractions-with-functions ├── 1.1-introduction.md ├── 1.2-the-elements-of-programming.md ├── 1.3-define-new-functions.md ├── 1.4-pratical-guidance-the-art-of-the-function.md ├── 1.5-control.md └── 1.6-high-order-functions.md ├── 02-buiding-abstractions-with-objects ├── 2.1-introduction.md ├── 2.2-data-abstraction.md ├── 2.3-sequences.md ├── 2.4-mutable-data.md ├── 2.5-object-oriented-programming.md ├── 2.6-implementing-classes-and-objects.md └── 2.7-generic-operations.md ├── 03-the-structure-and-interpretation-of-computer-programs ├── 3.1-introduction.md ├── 3.2-functions-and-the-processes-they-generate.md ├── 3.3-recursive-data-structures.md ├── 3.4-exceptions.md ├── 3.5-interpreters-for-languages-with-combination.md └── 3.6-interpreters-for-languages-with-abstraction.md ├── 04-distributed-and-parallel-computing ├── 4.1-introduction.md ├── 4.2-distributed-computing.md └── 4.3-parallel-computing.md ├── 05-sequences-and-coroutines ├── 5.1-introduction.md ├── 5.2-implicit-sequences.md └── 5.3-coroutines.md ├── README.md ├── cover.jpg ├── ebook.epub ├── img ├── .DS_Store ├── 01 │ ├── .DS_Store │ ├── curves.png │ ├── evaluate_square.png │ ├── evaluate_sum_squares_0.png │ ├── evaluate_sum_squares_1.png │ ├── evaluate_sum_squares_2.png │ ├── evaluate_sum_squares_3.png │ ├── expression_tree.png │ ├── function_abs.png │ ├── function_print.png │ ├── global_frame.png │ ├── global_frame_assignment.png │ ├── global_frame_def.png │ ├── iter_improve_apply.png │ ├── iter_improve_global.png │ ├── newton.png │ ├── pi_sum.png │ ├── square_root.png │ └── square_root_update.png ├── 02 │ ├── .DS_Store │ ├── barriers.png │ ├── constraints.png │ ├── getitem_rlist_0.png │ ├── getitem_rlist_1.png │ ├── getitem_rlist_2.png │ ├── interface.png │ ├── lists.png │ ├── multiple_inheritance.png │ ├── nested_pairs.png │ ├── nonlocal_assign.png │ ├── nonlocal_call.png │ ├── nonlocal_call2.png │ ├── nonlocal_corefer.png │ ├── nonlocal_def.png │ ├── nonlocal_def2.png │ ├── nonlocal_recall.png │ ├── pair.png │ └── sequence.png ├── 03 │ ├── eval_apply.png │ ├── fact.png │ ├── factorial_machine.png │ ├── fib.png │ ├── fib_env.png │ ├── logo_apply.png │ ├── logo_eval.png │ ├── pig_latin.png │ ├── scope.png │ ├── set_trees.png │ ├── sier.png │ ├── star.png │ ├── tree.png │ └── universal_machine.png ├── 04 │ ├── clientserver.png │ ├── deadlock.png │ ├── vector-math1.png │ ├── vector-math2.png │ └── vector-math3.png └── 05 │ ├── coroutine.png │ ├── produce-filter-consume.png │ ├── read-match-coroutine.png │ └── subroutine.png ├── introduction.md └── style.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/.DS_Store -------------------------------------------------------------------------------- /01-building-abstractions-with-functions/1.1-introduction.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 1: Building Abstractions with Functions 3 | 4 | ## 1.1 Introduction 5 | 6 | Computer science is a tremendously broad academic discipline. The areas of globally distributed systems, artificial intelligence, robotics, graphics, security, scientific computing, computer architecture, and dozens of emerging sub-fields each expand with new techniques and discoveries every year. The rapid progress of computer science has left few aspects of human life unaffected. Commerce, communication, science, art, leisure, and politics have all been reinvented as computational domains. 7 | 8 | The tremendous productivity of computer science is only possible because it is built upon an elegant and powerful set of fundamental ideas. All computing begins with representing information, specifying logic to process it, and designing abstractions that manage the complexity of that logic. Mastering these fundamentals will require us to understand precisely how computers interpret computer programs and carry out computational processes. 9 | 10 | These fundamental ideas have long been taught at Berkeley using the classic textbook *Structure and Interpretation of Computer Programs* ([SICP](http://mitpress.mit.edu/sicp)) by Harold Abelson and Gerald Jay Sussman with Julie Sussman. These lecture notes borrow heavily from that textbook, which the original authors have kindly licensed for adaptation and reuse. 11 | 12 | The embarkment of our intellectual journey requires no revision, nor should we expect that it ever will. 13 | 14 | > 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. 15 | > 16 | > 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. 17 | > 18 | > 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. 19 | > 20 | > — Abelson and Sussman, [SICP](http://mitpress.mit.edu/sicp) (1993) 21 | 22 | ### 1.1.1 Programming in Python 23 | 24 | > A language isn’t something you learn so much as something you join. 25 | > 26 | > —[Arika Okrent](http://arikaokrent.com/) 27 | 28 | In order to define computational processes, we need a programming language; preferably one many humans and a great variety of computers can all understand. In this course, we will learn the [Python](http://docs.python.org/py3k/) language. 29 | 30 | Python is a widely used programming language that has recruited enthusiasts from many professions: web programmers, game engineers, scientists, academics, and even designers of new programming languages. When you learn Python, you join a million-person-strong community of developers. Developer communities are tremendously important institutions: members help each other solve problems, share their code and experiences, and collectively develop software and tools. Dedicated members often achieve celebrity and widespread esteem for their contributions. Perhaps someday you will be named among these elite Pythonistas. 31 | 32 | The Python language itself is the product of a [large volunteer community](http://www.python.org/psf/members/) that prides itself on the [diversity](http://python.org/community/diversity/) of its contributors. The language was conceived and first implemented by [Guido van Rossum](http://en.wikipedia.org/wiki/Guido_van_Rossum) in the late 1980's. The first chapter of his [Python 3 Tutorial](http://docs.python.org/py3k/tutorial/appetite.html) explains why Python is so popular, among the many languages available today. 33 | 34 | Python excels as an instructional language because, throughout its history, Python's developers have emphasized the human interpretability of Python code, reinforced by the [Zen of Python](http://www.python.org/dev/peps/pep-0020/) guiding principles of beauty, simplicity, and readability. Python is particularly appropriate for this course because its broad set of features support a variety of different programming styles, which we will explore. While there is no single way to program in Python, there are a set of conventions shared across the developer community that facilitate the process of reading, understanding, and extending existing programs. Hence, Python's combination of great flexibility and accessibility allows students to explore many programming paradigms, and then apply their newly acquired knowledge to thousands of [ongoing projects](http://pypi.python.org/pypi). 35 | 36 | These notes maintain the spirit of [SICP](http://mitpress.mit.edu/sicp) by introducing the features of Python in lock step with techniques for abstraction design and a rigorous model of computation. In addition, these notes provide a practical introduction to Python programming, including some advanced language features and illustrative examples. Learning Python will come naturally as you progress through the course. 37 | 38 | However, Python is a rich language with many features and uses, and we consciously introduce them slowly as we layer on fundamental computer science concepts. For experienced students who want to inhale all of the details of the language quickly, we recommend reading Mark Pilgrim's book [Dive Into Python 3](http://diveintopython3.ep.io/), which is freely available online. The topics in that book differ substantially from the topics of this course, but the book contains very valuable practical information on using the Python language. Be forewarned: unlike these notes, Dive Into Python 3 assumes substantial programming experience. 39 | 40 | The best way to get started programming in Python is to interact with the interpreter directly. This section describes how to install Python 3, initiate an interactive session with the interpreter, and start programming. 41 | 42 | ### 1.1.2 Installing Python 3 43 | 44 | As with all great software, Python has many versions. This course will use the most recent stable version of Python 3 (currently Python 3.2). Many computers have older versions of Python installed already, but those will not suffice for this course. You should be able to use any computer for this course, but expect to install Python 3. Don't worry, Python is free. 45 | 46 | Dive Into Python 3 has detailed [installation instructions](http://diveintopython3.ep.io/installing-python.html) for all major platforms. These instructions mention Python 3.1 several times, but you're better off with Python 3.2 (although the differences are insignificant for this course). All instructional machines in the EECS department have Python 3.2 already installed. 47 | 48 | ### 1.1.3 Interactive Sessions 49 | 50 | In an interactive Python session, you type some Python *code* after the *prompt*, `>>>`. The Python *interpreter* reads and evaluates what you type, carrying out your various commands. 51 | 52 | There are several ways to start an interactive session, and they differ in their properties. Try them all to find out what you prefer. They all use exactly the same interpreter behind the scenes. 53 | 54 | - The simplest and most common way is to run the Python 3 application. Type `python3` at a terminal prompt (Mac/Unix/Linux) or open the Python 3 application in Windows. 55 | - A more user-friendly application for those learning the language is called Idle 3 (`idle3`). Idle colorizes your code (called syntax highlighting), pops up usage hints, and marks the source of some errors. Idle is always bundled with Python, so you have already installed it. 56 | - The Emacs editor can run an interactive session inside one of its buffers. While slightly more challenging to learn, Emacs is a powerful and versatile editor for any programming language. Read the 61A Emacs Tutorial to get started. Many programmers who invest the time to learn Emacs never switch editors again. 57 | 58 | In any case, if you see the Python prompt, `>>>`, then you have successfully started an interactive session. These notes depict example interactions using the prompt, followed by some input. 59 | 60 | ``` 61 | >>> 2 + 2 62 | 4 63 | ``` 64 | 65 | Controls: Each session keeps a history of what you have typed. To access that history, press `-P` (previous) and `-N` (next). `-D` exits a session, which discards this history. 66 | 67 | ### 1.1.4 First Example 68 | 69 | > And, as imagination bodies forthThe forms of things to unknown, and the poet's penTurns them to shapes, and gives to airy nothingA local habitation and a name. 70 | > 71 | > — William Shakespeare, A Midsummer-Night's Dream 72 | 73 | To give Python the introduction it deserves, we will begin with an example that uses several language features. In the next section, we will have to start from scratch and build up the language piece by piece. Think of this section as a sneak preview of powerful features to come. 74 | 75 | Python has built-in support for a wide range of common programming activities, like manipulating text, displaying graphics, and communicating over the Internet. The import statement 76 | 77 | ``` 78 | >>> from urllib.request import urlopen 79 | ``` 80 | 81 | loads functionality for accessing data on the Internet. In particular, it makes available a function called `urlopen`, which can access the content at a uniform resource locator (URL), which is a location of something on the Internet. 82 | 83 | **Statements & Expressions**. Python code consists of statements and expressions. Broadly, computer programs consist of instructions to either 84 | 85 | 1. Compute some value 86 | 2. Carry out some action 87 | 88 | Statements typically describe actions. When the Python interpreter executes a statement, it carries out the corresponding action. On the other hand, expressions typically describe computations that yield values. When Python evaluates an expression, it computes its value. This chapter introduces several types of statements and expressions. 89 | 90 | The assignment statement 91 | 92 | ``` 93 | >>> shakespeare = urlopen('http://inst.eecs.berkeley.edu/~cs61a/fa11/shakespeare.txt') 94 | ``` 95 | 96 | associates the name `shakespeare` with the value of the expression that follows. That expression applies the `urlopen` function to a URL that contains the complete text of William Shakespeare's 37 plays, all in a single text document. 97 | 98 | **Functions**. Functions encapsulate logic that manipulates data. A web address is a piece of data, and the text of Shakespeare's plays is another. The process by which the former leads to the latter may be complex, but we can apply that process using only a simple expression because that complexity is tucked away within a function. Functions are the primary topic of this chapter. 99 | 100 | Another assignment statement 101 | 102 | ``` 103 | >>> words = set(shakespeare.read().decode().split()) 104 | ``` 105 | 106 | associates the name `words` to the set of all unique words that appear in Shakespeare's plays, all 33,721 of them. The chain of commands to `read`, `decode`, and `split`, each operate on an intermediate computational entity: data is read from the opened URL, that data is decoded into text, and that text is split into words. All of those words are placed in a `set`. 107 | 108 | **Objects**. A set is a type of object, one that supports set operations like computing intersections and testing membership. An object seamlessly bundles together data and the logic that manipulates that data, in a way that hides the complexity of both. Objects are the primary topic of Chapter 2. 109 | 110 | The expression 111 | 112 | ``` 113 | >>> {w for w in words if len(w) >= 5 and w[::-1] in words} 114 | {'madam', 'stink', 'leets', 'rever', 'drawer', 'stops', 'sessa', 115 | 'repaid', 'speed', 'redder', 'devil', 'minim', 'spots', 'asses', 116 | 'refer', 'lived', 'keels', 'diaper', 'sleek', 'steel', 'leper', 117 | 'level', 'deeps', 'repel', 'reward', 'knits'} 118 | ``` 119 | 120 | is a compound expression that evaluates to the set of Shakespearian words that appear both forward and in reverse. The cryptic notation `w[::-1]` enumerates each letter in a word, but the `-1` says to step backwards (`::` here means that the positions of the first and last characters to enumerate are defaulted.) When you enter an expression in an interactive session, Python prints its value on the following line, as shown. 121 | 122 | **Interpreters**. Evaluating compound expressions requires a precise procedure that interprets code in a predictable way. A program that implements such a procedure, evaluating compound expressions and statements, is called an interpreter. The design and implementation of interpreters is the primary topic of Chapter 3. 123 | 124 | When compared with other computer programs, interpreters for programming languages are unique in their generality. Python was not designed with Shakespeare or palindromes in mind. However, its great flexibility allowed us to process a large amount of text with only a few lines of code. 125 | 126 | In the end, we will find that all of these core concepts are closely related: functions are objects, objects are functions, and interpreters are instances of both. However, developing a clear understanding of each of these concepts and their role in organizing code is critical to mastering the art of programming. 127 | 128 | ### 1.1.5 Practical Guidance: Errors 129 | 130 | Python is waiting for your command. You are encouraged to experiment with the language, even though you may not yet know its full vocabulary and structure. However, be prepared for errors. While computers are tremendously fast and flexible, they are also extremely rigid. The nature of computers is described in [Stanford's introductory course](http://www.stanford.edu/class/cs101/code-introduction.html) as 131 | 132 | > The fundamental equation of computers is: `computer = powerful + stupid` 133 | > 134 | > Computers are very powerful, looking at volumes of data very quickly. Computers can perform billions of operations per second, where each operation is pretty simple. 135 | > 136 | > Computers are also shockingly stupid and fragile. The operations that they can do are extremely rigid, simple, and mechanical. The computer lacks anything like real insight .. it's nothing like the HAL 9000 from the movies. If nothing else, you should not be intimidated by the computer as if it's some sort of brain. It's very mechanical underneath it all. 137 | > 138 | > Programming is about a person using their real insight to build something useful, constructed out of these teeny, simple little operations that the computer can do. 139 | > 140 | > — Francisco Cai and Nick Parlante, Stanford CS101 141 | 142 | The rigidity of computers will immediately become apparent as you experiment with the Python interpreter: even the smallest spelling and formatting changes will cause unexpected outputs and errors. 143 | 144 | Learning to interpret errors and diagnose the cause of unexpected errors is called *debugging*. Some guiding principles of debugging are: 145 | 146 | 1. **Test incrementally**: Every well-written program is composed of small, modular components that can be tested individually. Test everything you write as soon as possible to catch errors early and gain confidence in your components. 147 | 2. **Isolate errors**: An error in the output of a compound program, expression, or statement can typically be attributed to a particular modular component. When trying to diagnose a problem, trace the error to the smallest fragment of code you can before trying to correct it. 148 | 3. **Check your assumptions**: Interpreters do carry out your instructions to the letter --- no more and no less. Their output is unexpected when the behavior of some code does not match what the programmer believes (or assumes) that behavior to be. Know your assumptions, then focus your debugging effort on verifying that your assumptions actually hold. 149 | 4. **Consult others**: You are not alone! If you don't understand an error message, ask a friend, instructor, or search engine. If you have isolated an error, but can't figure out how to correct it, ask someone else to take a look. A lot of valuable programming knowledge is shared in the context of team problem solving. 150 | 151 | Incremental testing, modular design, precise assumptions, and teamwork are themes that persist throughout this course. Hopefully, they will also persist throughout your computer science career. -------------------------------------------------------------------------------- /01-building-abstractions-with-functions/1.2-the-elements-of-programming.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.2 The Elements of Programming 3 | 4 | A programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about processes. Programs serve to communicate those ideas among the members of a programming community. Thus, programs must be written for people to read, and only incidentally for machines to execute. 5 | 6 | When we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this: 7 | 8 | - **primitive expressions and statements**, which represent the simplest building blocks that the language provides, 9 | - **means of combination**, by which compound elements are built from simpler ones, and 10 | - **means of abstraction**, by which compound elements can be named and manipulated as units. 11 | 12 | In programming, we deal with two kinds of elements: functions and data. (Soon we will discover that they are really not so distinct.) Informally, data is stuff that we want to manipulate, and functions describe the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive functions and should have methods for combining and abstracting both functions and data. 13 | 14 | ### 1.2.1 Expressions 15 | 16 | Having experimented with the full Python interpreter, we now must start anew, methodically developing the Python language piece by piece. Be patient if the examples seem simplistic --- more exciting material is soon to come. 17 | 18 | We begin with primitive expressions. One kind of primitive expression is a number. More precisely, the expression that you type consists of the numerals that represent the number in base 10. 19 | 20 | ``` 21 | >>> 42 22 | 42 23 | ``` 24 | 25 | Expressions representing numbers may be combined with mathematical operators to form a compound expression, which the interpreter will evaluate: 26 | 27 | ``` 28 | >>> -1 - -1 29 | 0 30 | >>> 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128 31 | 0.9921875 32 | ``` 33 | 34 | These mathematical expressions use *infix* notation, where the *operator* (e.g., `+`, `-`, `*`, or `/`) appears in between the *operands* (numbers). Python includes many ways to form compound expressions. Rather than attempt to enumerate them all immediately, we will introduce new expression forms as we go, along with the language features that they support. 35 | 36 | ### 1.2.2 Call Expressions 37 | 38 | The most important kind of compound expression is a *call expression*, which applies a function to some arguments. Recall from algebra that the mathematical notion of a function is a mapping from some input arguments to an output value. For instance, the `max` function maps its inputs to a single output, which is the largest of the inputs. A function in Python is more than just an input-output mapping; it describes a computational process. However, the way in which Python expresses function application is the same as in mathematics. 39 | 40 | ``` 41 | >>> max(7.5, 9.5) 42 | 9.5 43 | ``` 44 | 45 | This call expression has subexpressions: the operator precedes parentheses, which enclose a comma-delimited list of operands. The operator must be a function. The operands can be any values; in this case they are numbers. When this call expression is evaluated, we say that the function `max` is *called* with arguments 7.5 and 9.5, and *returns* a value of 9.5. 46 | 47 | The order of the arguments in a call expression matters. For instance, the function `pow` raises its first argument to the power of its second argument. 48 | 49 | ``` 50 | >>> pow(100, 2) 51 | 10000 52 | >>> pow(2, 100) 53 | 1267650600228229401496703205376 54 | ``` 55 | 56 | Function notation has several advantages over the mathematical convention of infix notation. First, functions may take an arbitrary number of arguments: 57 | 58 | ``` 59 | >>> max(1, -2, 3, -4) 60 | 3 61 | ``` 62 | 63 | No ambiguity can arise, because the function name always precedes its arguments. 64 | 65 | Second, function notation extends in a straightforward way to *nested* expressions, where the elements are themselves compound expressions. In nested call expressions, unlike compound infix expressions, the structure of the nesting is entirely explicit in the parentheses. 66 | 67 | ``` 68 | >>> max(min(1, -2), min(pow(3, 5), -4)) 69 | -2 70 | ``` 71 | 72 | There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Python interpreter can evaluate. However, humans quickly get confused by multi-level nesting. An important role for you as a programmer is to structure expressions so that they remain interpretable by yourself, your programming partners, and others who may read your code in the future. 73 | 74 | Finally, mathematical notation has a great variety of forms: multiplication appears between terms, exponents appear as superscripts, division as a horizontal bar, and a square root as a roof with slanted siding. Some of this notation is very hard to type! However, all of this complexity can be unified via the notation of call expressions. While Python supports common mathematical operators using infix notation (like `+` and `-`), any operator can be expressed as a function with a name. 75 | 76 | ### 1.2.3 Importing Library Functions 77 | 78 | Python defines a very large number of functions, including the operator functions mentioned in the preceding section, but does not make their names available by default, so as to avoid complete chaos. Instead, it organizes the functions and other quantities that it knows about into modules, which together comprise the Python Library. To use these elements, one imports them. For example, the `math` module provides a variety of familiar mathematical functions: 79 | 80 | ``` 81 | >>> from math import sqrt, exp 82 | >>> sqrt(256) 83 | 16.0 84 | >>> exp(1) 85 | 2.718281828459045 86 | ``` 87 | 88 | and the `operator` module provides access to functions corresponding to infix operators: 89 | 90 | ``` 91 | >>> from operator import add, sub, mul 92 | >>> add(14, 28) 93 | 42 94 | >>> sub(100, mul(7, add(8, 4))) 95 | 16 96 | ``` 97 | 98 | An `import` statement designates a module name (e.g., `operator` or `math`), and then lists the named attributes of that module to import (e.g., `sqrt` or `exp`). 99 | 100 | The [Python 3 Library Docs](http://docs.python.org/py3k/library/index.html) list the functions defined by each module, such as the [math module](http://docs.python.org/py3k/library/math.html). However, this documentation is written for developers who know the whole language well. For now, you may find that experimenting with a function tells you more about its behavior than reading the documemtation. As you become familiar with the Python language and vocabulary, this documentation will become a valuable reference source. 101 | 102 | ### 1.2.4 Names and the Environment 103 | 104 | A critical aspect of a programming language is the means it provides for using names to refer to computational objects. If a value has been given a name, we say that the name *binds* to the value. 105 | 106 | In Python, we can establish new bindings using the assignment statement, which contains a name to the left of `=` and a value to the right: 107 | 108 | ``` 109 | >>> radius = 10 110 | >>> radius 111 | 10 112 | >>> 2 * radius 113 | 20 114 | ``` 115 | 116 | Names are also bound via `import` statements. 117 | 118 | ``` 119 | >>> from math import pi 120 | >>> pi * 71 / 223 121 | 1.0002380197528042 122 | ``` 123 | 124 | We can also assign multiple values to multiple names in a single statement, where names and expressions are separated by commas. 125 | 126 | ``` 127 | >>> area, circumference = pi * radius * radius, 2 * pi * radius 128 | >>> area 129 | 314.1592653589793 130 | >>> circumference 131 | 62.83185307179586 132 | ``` 133 | 134 | The `=` symbol is called the *assignment* operator in Python (and many other languages). Assignment is Python's simplest means of *abstraction*, for it allows us to use simple names to refer to the results of compound operations, such as the `area` computed above. In this way, complex programs are constructed by building, step by step, computational objects of increasing complexity. 135 | 136 | The possibility of binding names to values and later retrieving those values by name means that the interpreter must maintain some sort of memory that keeps track of the names, values, and bindings. This memory is called an *environment*. 137 | 138 | Names can also be bound to functions. For instance, the name `max` is bound to the max function we have been using. Functions, unlike numbers, are tricky to render as text, so Python prints an identifying description instead, when asked to print a function: 139 | 140 | ``` 141 | >>> max 142 | 143 | ``` 144 | 145 | We can use assignment statements to give new names to existing functions. 146 | 147 | ``` 148 | >>> f = max 149 | >>> f 150 | 151 | >>> f(3, 4) 152 | 4 153 | ``` 154 | 155 | And successive assignment statements can rebind a name to a new value. 156 | 157 | ``` 158 | >>> f = 2 159 | >>> f 160 | 2 161 | ``` 162 | 163 | In Python, the names bound via assignment are often called *variable names* because they can be bound to a variety of different values in the course of executing a program. 164 | 165 | ### 1.2.5 Evaluating Nested Expressions 166 | 167 | One of our goals in this chapter is to isolate issues about thinking procedurally. As a case in point, let us consider that, in evaluating nested call expressions, the interpreter is itself following a procedure. 168 | 169 | To evaluate a call expression, Python will do the following: 170 | 171 | 1. Evaluate the operator and operand subexpressions, then 172 | 2. Apply the function that is the value of the operator subexpression to the arguments that are the values of the operand subexpressions. 173 | 174 | Even this simple procedure illustrates some important points about processes in general. The first step dictates that in order to accomplish the evaluation process for a call expression we must first evaluate other expressions. Thus, the evaluation procedure is *recursive* in nature; that is, it includes, as one of its steps, the need to invoke the rule itself. 175 | 176 | For example, evaluating 177 | 178 | ``` 179 | >>> mul(add(2, mul(4, 6)), add(3, 5)) 180 | 208 181 | ``` 182 | 183 | requires that this evaluation procedure be applied four times. If we draw each expression that we evaluate, we can visualize the hierarchical structure of this process. 184 | 185 | ![](img/01/expression_tree.png) 186 | 187 | This illustration is called an *expression tree*. In computer science, trees grow from the top down. The objects at each point in a tree are called nodes; in this case, they are expressions paired with their values. 188 | 189 | Evaluating its root, the full expression, requires first evaluating the branches that are its subexpressions. The leaf expressions (that is, nodes with no branches stemming from them) represent either functions or numbers. The interior nodes have two parts: the call expression to which our evaluation rule is applied, and the result of that expression. Viewing evaluation in terms of this tree, we can imagine that the values of the operands percolate upward, starting from the terminal nodes and then combining at higher and higher levels. 190 | 191 | Next, observe that the repeated application of the first step brings us to the point where we need to evaluate, not call expressions, but primitive expressions such as numerals (e.g., `2`) and names (e.g., `add`). We take care of the primitive cases by stipulating that 192 | 193 | - A numeral evaluates to the number it names, 194 | - A name evaluates to the value associated with that name in the current environment. 195 | 196 | Notice the important role of an environment in determining the meaning of the symbols in expressions. In Python, it is meaningless to speak of the value of an expression such as 197 | 198 | ``` 199 | >>> add(x, 1) 200 | ``` 201 | 202 | without specifying any information about the environment that would provide a meaning for the name `x` (or even for the name `add`). Environments provide the context in which evaluation takes place, which plays an important role in our understanding of program execution. 203 | 204 | This evaluation procedure does not suffice to evaluate all Python code, only call expressions, numerals, and names. For instance, it does not handle assignment statements. Executing 205 | 206 | ``` 207 | >>> x = 3 208 | ``` 209 | 210 | does not return a value nor evaluate a function on some arguments, since the purpose of assignment is instead to bind a name to a value. In general, statements are not evaluated but *executed*; they do not produce a value but instead make some change. Each type of statement or expression has its own evaluation or execution procedure, which we will introduce incrementally as we proceed. 211 | 212 | A pedantic note: when we say that "a numeral evaluates to a number," we actually mean that the Python interpreter evaluates a numeral to a number. It is the interpreter which endows meaning to the programming language. Given that the interpreter is a fixed program that always behaves consistently, we can loosely say that numerals (and expressions) themselves evaluate to values in the context of Python programs. 213 | 214 | ### 1.2.6 Function Diagrams 215 | 216 | As we continue to develop a formal model of evaluation, we will find that diagramming the internal state of the interpreter helps us track the progress of our evaluation procedure. An essential part of these diagrams is a representation of a function. 217 | 218 | **Pure functions.** Functions have some input (their arguments) and return some output (the result of applying them). The built-in function 219 | 220 | ``` 221 | >>> abs(-2) 222 | 2 223 | ``` 224 | 225 | can be depicted as a small machine that takes input and produces output. 226 | 227 | ![](img/01/function_abs.png) 228 | 229 | The function `abs` is *pure*. Pure functions have the property that applying them has no effects beyond returning a value. 230 | 231 | **Non-pure functions.** In addition to returning a value, applying a non-pure function can generate *side effects*, which make some change to the state of the interpreter or computer. A common side effect is to generate additional output beyond the return value, using the `print` function. 232 | 233 | ``` 234 | >>> print(-2) 235 | -2 236 | >>> print(1, 2, 3) 237 | 1 2 3 238 | ``` 239 | 240 | While `print` and `abs` may appear to be similar in these examples, they work in fundamentally different ways. The value that `print` returns is always `None`, a special Python value that represents nothing. The interactive Python interpreter does not automatically print the value `None`. In the case of `print`, the function itself is printing output as a side effect of being called. 241 | 242 | ![](img/01/function_print.png) 243 | 244 | A nested expression of calls to `print` highlights the non-pure character of the function. 245 | 246 | ``` 247 | >>> print(print(1), print(2)) 248 | 1 249 | 2 250 | None None 251 | ``` 252 | 253 | If you find this output to be unexpected, draw an expression tree to clarify why evaluating this expression produces this peculiar output. 254 | 255 | Be careful with `print`! The fact that it returns `None` means that it *should not* be the expression in an assignment statement. 256 | 257 | ``` 258 | >>> two = print(2) 259 | 2 260 | >>> print(two) 261 | None 262 | ``` 263 | 264 | **Signatures.** Functions differ in the number of arguments that they are allowed to take. To track these requirements, we draw each function in a way that shows the function name and names of its arguments. The function `abs` takes only one argument called `number`; providing more or fewer will result in an error. The function `print` can take an arbitrary number of arguments, hence its rendering as `print(...)`. A description of the arguments that a function can take is called the function's *signature*. -------------------------------------------------------------------------------- /01-building-abstractions-with-functions/1.3-define-new-functions.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.3 Defining New Functions 3 | 4 | We have identified in Python some of the elements that must appear in any powerful programming language: 5 | 6 | 1. Numbers and arithmetic operations are built-in data and functions. 7 | 2. Nested function application provides a means of combining operations. 8 | 3. Binding names to values provides a limited means of abstraction. 9 | 10 | Now we will learn about *function definitions*, a much more powerful abstraction technique by which a name can be bound to compound operation, which can then be referred to as a unit. 11 | 12 | We begin by examining how to express the idea of "squaring." We might say, "To square something, multiply it by itself." This is expressed in Python as 13 | 14 | ``` 15 | >>> def square(x): 16 | return mul(x, x) 17 | ``` 18 | 19 | which defines a new function that has been given the name `square`. This user-defined function is not built into the interpreter. It represents the compound operation of multiplying something by itself. The `x` in this definition is called a *formal parameter*, which provides a name for the thing to be multiplied. The definition creates this user-defined function and associates it with the name `square`. 20 | 21 | Function definitions consist of a `def` statement that indicates a `` and a list of named ``, then a `return` statement, called the function body, that specifies the `` of the function, which is an expression to be evaluated whenever the function is applied. 22 | 23 | > `def ():` 24 | > 25 | > `return ` 26 | 27 | The second line *must* be indented! Convention dictates that we indent with four spaces, rather than a tab. The return expression is not evaluated right away; it is stored as part of the newly defined function and evaluated only when the function is eventually applied. (Soon, we will see that the indented region can span multiple lines.) 28 | 29 | Having defined `square`, we can apply it with a call expression: 30 | 31 | ``` 32 | >>> square(21) 33 | 441 34 | >>> square(add(2, 5)) 35 | 49 36 | >>> square(square(3)) 37 | 81 38 | ``` 39 | 40 | We can also use `square` as a building block in defining other functions. For example, we can easily define a function `sum_squares` that, given any two numbers as arguments, returns the sum of their squares: 41 | 42 | ``` 43 | >>> def sum_squares(x, y): 44 | return add(square(x), square(y)) 45 | >>> sum_squares(3, 4) 46 | 25 47 | ``` 48 | 49 | User-defined functions are used in exactly the same way as built-in functions. Indeed, one cannot tell from the definition of `sum_squares` whether `square` is built into the interpreter, imported from a module, or defined by the user. 50 | 51 | ### 1.3.1 Environments 52 | 53 | Our subset of Python is now complex enough that the meaning of programs is non-obvious. What if a formal parameter has the same name as a built-in function? Can two functions share names without confusion? To resolve such questions, we must describe environments in more detail. 54 | 55 | An environment in which an expression is evaluated consists of a sequence of *frames*, depicted as boxes. Each frame contains *bindings*, which associate a name with its corresponding value. There is a single *global* frame that contains name bindings for all built-in functions (only `abs` and `max` are shown). We indicate the global frame with a globe symbol. 56 | 57 | ![](img/01/global_frame.png) 58 | 59 | Assignment and import statements add entries to the first frame of the current environment. So far, our environment consists only of the global frame. 60 | 61 | ``` 62 | >>> from math import pi 63 | >>> tau = 2 * pi 64 | ``` 65 | 66 | ![](img/01/global_frame_assignment.png) 67 | 68 | A `def` statement also binds a name to the function created by the definition. The resulting environment after defining `square` appears below: 69 | 70 | ![](img/01/global_frame_def.png) 71 | 72 | These *environment diagrams* show the bindings of the current environment, along with the values (which are not part of any frame) to which names are bound. Notice that the name of a function is repeated, once in the frame, and once as part of the function itself. This repetition is intentional: many different names may refer to the same function, but that function itself has only one intrinsic name. However, looking up the value for a name in an environment only inspects name bindings. The intrinsic name of a function **does not** play a role in looking up names. In the example we saw earlier, 73 | 74 | ``` 75 | >>> f = max 76 | >>> f 77 | 78 | ``` 79 | 80 | The name *max* is the intrinsic name of the function, and that's what you see printed as the value for `f`. In addition, both the names `max` and `f` are bound to that same function in the global environment. 81 | 82 | As we proceed to introduce additional features of Python, we will have to extend these diagrams. Every time we do, we will list the new features that our diagrams can express. 83 | 84 | **New environment Features:** Assignment and user-defined function definition. 85 | 86 | ### 1.3.2 Calling User-Defined Functions 87 | 88 | To evaluate a call expression whose operator names a user-defined function, the Python interpreter follows a process similar to the one for evaluating expressions with a built-in operator function. That is, the interpreter evaluates the operand expressions, and then applies the named function to the resulting arguments. 89 | 90 | The act of applying a user-defined function introduces a second *local* frame, which is only accessible to that function. To apply a user-defined function to some arguments: 91 | 92 | 1. Bind the arguments to the names of the function's formal parameters in a new *local* frame. 93 | 2. Evaluate the body of the function in the environment beginning at that frame and ending at the global frame. 94 | 95 | The environment in which the body is evaluated consists of two frames: first the local frame that contains argument bindings, then the global frame that contains everything else. Each instance of a function application has its own independent local frame. 96 | 97 | ![](img/01/evaluate_square.png) 98 | 99 | This figure includes two different aspects of the Python interpreter: the current environment, and a part of the expression tree related to the current line of code being evaluated. We have depicted the evaluation of a call expression that has a user-defined function (in blue) as a two-part rounded rectangle. Dotted arrows indicate which environment is used to evaluate the expression in each part. 100 | 101 | - The top half shows the call expression being evaluated. This call expression is not internal to any function, so it is evaluated in the global environment. Thus, any names within it (such as `square`) are looked up in the global frame. 102 | - The bottom half shows the body of the `square` function. Its return expression is evaluated in the new environment introduced by step 1 above, which binds the name of `square`'s formal parameter `x` to the value of its argument, `-2`. 103 | 104 | The order of frames in an environment affects the value returned by looking up a name in an expression. We stated previously that a name is evaluated to the value associated with that name in the current environment. We can now be more precise: 105 | 106 | - A name evaluates to the value bound to that name in the earliest frame of the current environment in which that name is found. 107 | 108 | Our conceptual framework of environments, names, and functions constitutes a *model of evaluation*; while some mechanical details are still unspecified (e.g., how a binding is implemented), our model does precisely and correctly describe how the interpreter evaluates call expressions. In Chapter 3 we shall see how this model can serve as a blueprint for implementing a working interpreter for a programming language. 109 | 110 | **New environment Feature:** Function application. 111 | 112 | ### 1.3.3 Example: Calling a User-Defined Function 113 | 114 | Let us again consider our two simple definitions: 115 | 116 | ``` 117 | >>> from operator import add, mul 118 | >>> def square(x): 119 | return mul(x, x) 120 | >>> def sum_squares(x, y): 121 | return add(square(x), square(y)) 122 | ``` 123 | 124 | ![](img/01/evaluate_sum_squares_0.png) 125 | 126 | And the process that evaluates the following call expression: 127 | 128 | ``` 129 | >>> sum_squares(5, 12) 130 | 169 131 | ``` 132 | 133 | Python first evaluates the name `sum_squares`, which is bound to a user-defined function in the global frame. The primitive numeric expressions 5 and 12 evaluate to the numbers they represent. 134 | 135 | Next, Python applies `sum_squares`, which introduces a local frame that binds x to 5 and y to 12. 136 | 137 | ![](img/01/evaluate_sum_squares_1.png) 138 | 139 | In this diagram, the local frame points to its successor, the global frame. All local frames must point to a predecessor, and these links define the sequence of frames that is the current environment. 140 | 141 | The body of `sum_squares` contains this call expression: 142 | 143 | ``` 144 | add ( square(x) , square(y) ) 145 | ________ _________ _________ 146 | "operator" "operand 0" "operand 1" 147 | ``` 148 | 149 | All three subexpressions are evalauted in the current environment, which begins with the frame labeled *sum_squares*. The operator subexpression `add` is a name found in the global frame, bound to the built-in function for addition. The two operand subexpressions must be evaluated in turn, before addition is applied. Both operands are evaluated in the current environment beginning with the frame labeled `sum_squares`. In the following environment diagrams, we will call this frame `A` and replace arrows pointing to this frame with the label `A` as well. 150 | 151 | In `operand 0`, `square` names a user-defined function in the global frame, while `x` names the number 5 in the local frame. Python applies `square` to 5 by introducing yet another local frame that binds x to 5. 152 | 153 | ![](img/01/evaluate_sum_squares_2.png) 154 | 155 | Using this local frame, the body expression `mul(x, x)` evaluates to 25. 156 | 157 | Our evaluation procedure now turns to `operand 1`, for which `y` names the number 12. Python evaluates the body of `square` again, this time introducing yet another local environment frame that binds `x` to 12. Hence, `operand 1` evaluates to 144. 158 | 159 | ![](img/01/evaluate_sum_squares_3.png) 160 | 161 | Finally, applying addition to the arguments 25 and 144 yields a final value for the body of `sum_squares`: 169. 162 | 163 | This figure, while complex, serves to illustrate many of the fundamental ideas we have developed so far. Names are bound to values, which spread across many local frames that all precede a single global frame that contains shared names. Expressions are tree-structured, and the environment must be augmented each time a subexpression contains a call to a user-defined function. 164 | 165 | All of this machinery exists to ensure that names resolve to the correct values at the correct points in the expression tree. This example illustrates why our model requires the complexity that we have introduced. All three local frames contain a binding for the name `x`, but that name is bound to different values in different frames. Local frames keep these names separate. 166 | 167 | ### 1.3.4 Local Names 168 | 169 | One detail of a function's implementation that should not affect the function's behavior is the implementer's choice of names for the function's formal parameters. Thus, the following functions should provide the same behavior: 170 | 171 | ``` 172 | >>> def square(x): 173 | return mul(x, x) 174 | >>> def square(y): 175 | return mul(y, y) 176 | ``` 177 | 178 | This principle -- that the meaning of a function should be independent of the parameter names chosen by its author -- has important consequences for programming languages. The simplest consequence is that the parameter names of a function must remain local to the body of the function. 179 | 180 | If the parameters were not local to the bodies of their respective functions, then the parameter `x` in `square` could be confused with the parameter `x` in `sum_squares`. Critically, this is not the case: the binding for `x` in different local frames are unrelated. Our model of computation is carefully designed to ensure this independence. 181 | 182 | We say that the *scope* of a local name is limited to the body of the user-defined function that defines it. When a name is no longer accessible, it is out of scope. This scoping behavior isn't a new fact about our model; it is a consequence of the way environments work. 183 | 184 | ### 1.3.5 Practical Guidance: Choosing Names 185 | 186 | The interchangeabily of names does not imply that formal parameter names do not matter at all. To the contrary, well-chosen function and parameter names are essential for the human interpretability of function definitions! 187 | 188 | The following guidelines are adapted from the [style guide for Python code](http://www.python.org/dev/peps/pep-0008), which serves as a guide for all (non-rebellious) Python programmers. A shared set of conventions smooths communication among members of a programming community. As a side effect of following these conventions, you will find that your code becomes more internally consistent. 189 | 190 | 1. Function names should be lowercase, with words separated by underscores. Descriptive names are encouraged. 191 | 2. Function names typically evoke operations applied to arguments by the interpreter (e.g., `print`, `add`, `square`) or the name of the quantity that results (e.g., `max`, `abs`, `sum`). 192 | 3. Parameter names should be lowercase, with words separated by underscores. Single-word names are preferred. 193 | 4. Parameter names should evoke the role of the parameter in the function, not just the type of value that is allowed. 194 | 5. Single letter parameter names are acceptable when their role is obvious, but never use "l" (lowercase ell), "O" (capital oh), or "I" (capital i) to avoid confusion with numerals. 195 | 196 | Review these guidelines periodically as you write programs, and soon your names will be delightfully Pythonic. 197 | 198 | ### 1.3.6 Functions as Abstractions 199 | 200 | Though it is very simple, `sum_squares` exemplifies the most powerful property of user-defined functions. The function `sum_squares` is defined in terms of the function `square`, but relies only on the relationship that `square` defines between its input arguments and its output values. 201 | 202 | We can write `sum_squares` without concerning ourselves with *how* to square a number. The details of how the square is computed can be suppressed, to be considered at a later time. Indeed, as far as `sum_squares` is concerned, `square` is not a particular function body, but rather an abstraction of a function, a so-called functional abstraction. At this level of abstraction, any function that computes the square is equally good. 203 | 204 | Thus, considering only the values they return, the following two functions for squaring a number should be indistinguishable. Each takes a numerical argument and produces the square of that number as the value. 205 | 206 | ``` 207 | >>> def square(x): 208 | return mul(x, x) 209 | >>> def square(x): 210 | return mul(x, x-1) + x 211 | ``` 212 | 213 | In other words, a function definition should be able to suppress details. The users of the function may not have written the function themselves, but may have obtained it from another programmer as a "black box". A user should not need to know how the function is implemented in order to use it. The Python Library has this property. Many developers use the functions defined there, but few ever inspect their implementation. In fact, many implementations of Python Library functions are not written in Python at all, but instead in the C language. 214 | 215 | ### 1.3.7 Operators 216 | 217 | Mathematical operators (like + and -) provided our first example of a method of combination, but we have yet to define an evaluation procedure for expressions that contain these operators. 218 | 219 | Python expressions with infix operators each have their own evaluation procedures, but you can often think of them as short-hand for call expressions. When you see 220 | 221 | ``` 222 | >>> 2 + 3 223 | 5 224 | ``` 225 | 226 | simply consider it to be short-hand for 227 | 228 | ``` 229 | >>> add(2, 3) 230 | 5 231 | ``` 232 | 233 | Infix notation can be nested, just like call expressions. Python applies the normal mathematical rules of operator precedence, which dictate how to interpret a compound expression with multiple operators. 234 | 235 | ``` 236 | >>> 2 + 3 * 4 + 5 237 | 19 238 | ``` 239 | 240 | evaluates to the same result as 241 | 242 | ``` 243 | >>> add(add(2, mul(3, 4)) , 5) 244 | 19 245 | ``` 246 | 247 | The nesting in the call expression is more explicit than the operator version. Python also allows subexpression grouping with parentheses, to override the normal precedence rules or make the nested structure of an expression more explicit. 248 | 249 | ``` 250 | >>> (2 + 3) * (4 + 5) 251 | 45 252 | ``` 253 | 254 | evaluates to the same result as 255 | 256 | ``` 257 | >>> mul(add(2, 3), add(4, 5)) 258 | 45 259 | ``` 260 | 261 | You should feel free to use these operators and parentheses in your programs. Idiomatic Python prefers operators over call expressions for simple mathematical operations. -------------------------------------------------------------------------------- /01-building-abstractions-with-functions/1.4-pratical-guidance-the-art-of-the-function.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.4 Practical Guidance: The Art of the Function 3 | 4 | Functions are an essential ingredient of all programs, large and small, and serve as our primary medium to express computational processes in a programming language. So far, we have discussed the formal properties of functions and how they are applied. We now turn to the topic of what makes a good function. Fundamentally, the qualities of good functions all reinforce the idea that functions are abstractions. 5 | 6 | - Each function should have exactly one job. That job should be identifiable with a short name and characterizable in a single line of text. Functions that perform multiple jobs in sequence should be divided into multiple functions. 7 | - *Don't repeat yourself* is a central tenet of software engineering. The so-called DRY principle states that multiple fragments of code should not describe redundant logic. Instead, that logic should be implemented once, given a name, and applied multiple times. If you find yourself copying and pasting a block of code, you have probably found an opportunity for functional abstraction. 8 | - Functions should be defined generally. Squaring is not in the Python Library precisely because it is a special case of the `pow` function, which raises numbers to arbitrary powers. 9 | 10 | These guidelines improve the readability of code, reduce the number of errors, and often minimize the total amount of code written. Decomposing a complex task into concise functions is a skill that takes experience to master. Fortunately, Python provides several features to support your efforts. 11 | 12 | ### 1.4.1 Docstrings 13 | 14 | A function definition will often include documentation describing the function, called a *docstring*, which must be indented along with the function body. Docstrings are conventionally triple quoted. The first line describes the job of the function in one line. The following lines can describe arguments and clarify the behavior of the function: 15 | 16 | ``` 17 | >>> def pressure(v, t, n): 18 | """Compute the pressure in pascals of an ideal gas. 19 | 20 | Applies the ideal gas law: http://en.wikipedia.org/wiki/Ideal_gas_law 21 | 22 | v -- volume of gas, in cubic meters 23 | t -- absolute temperature in degrees kelvin 24 | n -- particles of gas 25 | """ 26 | k = 1.38e-23 # Boltzmann's constant 27 | return n * k * t / v 28 | ``` 29 | 30 | When you call `help` with the name of a function as an argument, you see its docstring (type `q` to quit Python help). 31 | 32 | ``` 33 | >>> help(pressure) 34 | ``` 35 | 36 | When writing Python programs, include docstrings for all but the simplest functions. Remember, code is written only once, but often read many times. The Python docs include [docstring guidelines](http://www.python.org/dev/peps/pep-0257/) that maintain consistency across different Python projects. 37 | 38 | ### 1.4.2 Default Argument Values 39 | 40 | A consequence of defining general functions is the introduction of additional arguments. Functions with many arguments can be awkward to call and difficult to read. 41 | 42 | In Python, we can provide default values for the arguments of a function. When calling that function, arguments with default values are optional. If they are not provided, then the default value is bound to the formal parameter name instead. For instance, if an application commonly computes pressure for one mole of particles, this value can be provided as a default: 43 | 44 | ``` 45 | >>> k_b=1.38e-23 # Boltzmann's constant 46 | >>> def pressure(v, t, n=6.022e23): 47 | """Compute the pressure in pascals of an ideal gas. 48 | 49 | v -- volume of gas, in cubic meters 50 | t -- absolute temperature in degrees kelvin 51 | n -- particles of gas (default: one mole) 52 | """ 53 | return n * k_b * t / v 54 | >>> pressure(1, 273.15) 55 | 2269.974834 56 | ``` 57 | 58 | Here, `pressure` is defined to take three arguments, but only two are provided in the call expression that follows. In this case, the value for `n` is taken from the `def` statement defaults (which looks like an assignment to `n`, although as this discussion suggests, it is more of a conditional assignment.) 59 | 60 | As a guideline, most data values used in a function's body should be expressed as default values to named arguments, so that they are easy to inspect and can be changed by the function caller. Some values that never change, like the fundamental constant `k_b`, can be defined in the global frame. -------------------------------------------------------------------------------- /01-building-abstractions-with-functions/1.5-control.md: -------------------------------------------------------------------------------- 1 | 2 | ## 1.5 Control 3 | 4 | The expressive power of the functions that we can define at this point is very limited, because we have not introduced a way to make tests and to perform different operations depending on the result of a test. *Control statements* will give us this capacity. Control statements differ fundamentally from the expressions that we have studied so far. They deviate from the strict evaluation of subexpressions from left to write, and get their name from the fact that they control what the interpreter should do next, possibly based on the values of expressions. 5 | 6 | ### 1.5.1 Statements 7 | 8 | So far, we have primarily considered how to evaluate expressions. However, we have seen three kinds of statements: assignment, `def`, and `return` statements. These lines of Python code are not themselves expressions, although they all contain expressions as components. 9 | 10 | To emphasize that the value of a statement is irrelevant (or nonexistant), we describe statements as being *executed* rather than evaluated. Each statement describes some change to the interpreter state, and executing a statement applies that change. As we have seen for `return` and assignment statements, executing statements can involve evaluating subexpressions contained within them. 11 | 12 | Expressions can also be executed as statements, in which case they are evaluated, but their value is discarded. Executing a pure function has no effect, but executing a non-pure function can cause effects as a consequence of function application. 13 | 14 | Consider, for instance, 15 | 16 | ``` 17 | >>> def square(x): 18 | mul(x, x) # Watch out! This call doesn't return a value. 19 | ``` 20 | 21 | This is valid Python, but probably not what was intended. The body of the function consists of an expression. An expression by itself is a valid statement, but the effect of the statement is that the `mul` function is called, and the result is discarded. If you want to do something with the result of an expression, you need to say so: you might store it with an assignment statement, or return it with a return statement: 22 | 23 | ``` 24 | >>> def square(x): 25 | return mul(x, x) 26 | ``` 27 | 28 | Sometimes it does make sense to have a function whose body is an expression, when a non-pure function like `print` is called. 29 | 30 | ``` 31 | >>> def print_square(x): 32 | print(square(x)) 33 | ``` 34 | 35 | At its highest level, the Python interpreter's job is to execute programs, composed of statements. However, much of the interesting work of computation comes from evaluating expressions. Statements govern the relationship among different expressions in a program and what happens to their results. 36 | 37 | ### 1.5.2 Compound Statements 38 | 39 | In general, Python code is a sequence of statements. A simple statement is a single line that doesn't end in a colon. A compound statement is so called because it is composed of other statements (simple and compound). Compound statements typically span multiple lines and start with a one-line header ending in a colon, which identifies the type of statement. Together, a header and an indented suite of statements is called a clause. A compound statement consists of one or more clauses: 40 | 41 | ``` 42 |
: 43 | 44 | 45 | ... 46 | : 47 | 48 | 49 | ... 50 | ... 51 | ``` 52 | 53 | We can understand the statements we have already introduced in these terms. 54 | 55 | - Expressions, return statements, and assignment statements are simple statements. 56 | - A `def` statement is a compound statement. The suite that follows the `def` header defines the function body. 57 | 58 | Specialized evaluation rules for each kind of header dictate when and if the statements in its suite are executed. We say that the header controls its suite. For example, in the case of `def` statements, we saw that the return expression is not evaluated immediately, but instead stored for later use when the defined function is eventually applied. 59 | 60 | We can also understand multi-line programs now. 61 | 62 | - To execute a sequence of statements, execute the first statement. If that statement does not redirect control, then proceed to execute the rest of the sequence of statements, if any remain. 63 | 64 | This definition exposes the essential structure of a recursively defined *sequence*: a sequence can be decomposed into its first element and the rest of its elements. The "rest" of a sequence of statements is itself a sequence of statements! Thus, we can recursively apply this execution rule. This view of sequences as recursive data structures will appear again in later chapters. 65 | 66 | The important consequence of this rule is that statements are executed in order, but later statements may never be reached, because of redirected control. 67 | 68 | **Practical Guidance.** When indenting a suite, all lines must be indented the same amount and in the same way (spaces, not tabs). Any variation in indentation will cause an error. 69 | 70 | ### 1.5.3 Defining Functions II: Local Assignment 71 | 72 | Originally, we stated that the body of a user-defined function consisted only of a `return` statement with a single return expression. In fact, functions can define a sequence of operations that extends beyond a single expression. The structure of compound Python statements naturally allows us to extend our concept of a function body to multiple statements. 73 | 74 | Whenever a user-defined function is applied, the sequence of clauses in the suite of its definition is executed in a local environment. A `return` statement redirects control: the process of function application terminates whenever the first `return` statement is executed, and the value of the `return` expression is the returned value of the function being applied. 75 | 76 | Thus, assignment statements can now appear within a function body. For instance, this function returns the absolute difference between two quantities as a percentage of the first, using a two-step calculation: 77 | 78 | ``` 79 | >>> def percent_difference(x, y): 80 | difference = abs(x-y) 81 | return 100 * difference / x 82 | >>> percent_difference(40, 50) 83 | 25.0 84 | ``` 85 | 86 | The effect of an assignment statement is to bind a name to a value in the *first* frame of the current environment. As a consequence, assignment statements within a function body cannot affect the global frame. The fact that functions can only manipulate their local environment is critical to creating *modular* programs, in which pure functions interact only via the values they take and return. 87 | 88 | Of course, the `percent_difference` function could be written as a single expression, as shown below, but the return expression is more complex. 89 | 90 | ``` 91 | >>> def percent_difference(x, y): 92 | return 100 * abs(x-y) / x 93 | ``` 94 | 95 | So far, local assignment hasn't increased the expressive power of our function definitions. It will do so, when combined with the control statements below. In addition, local assignment also plays a critical role in clarifying the meaning of complex expressions by assigning names to intermediate quantities. 96 | 97 | **New environment Feature:** Local assignment. 98 | 99 | ### 1.5.4 Conditional Statements 100 | 101 | Python has a built-in function for computing absolute values. 102 | 103 | ``` 104 | >>> abs(-2) 105 | 2 106 | ``` 107 | 108 | We would like to be able to implement such a function ourselves, but we cannot currently define a function that has a test and a choice. We would like to express that if `x` is positive, `abs(x)` returns `x`. Furthermore, if `x` is 0, `abs(x)` returns 0. Otherwise, `abs(x)` returns `-x`. In Python, we can express this choice with a conditional statement. 109 | 110 | ``` 111 | >>> def absolute_value(x): 112 | """Compute abs(x).""" 113 | if x > 0: 114 | return x 115 | elif x == 0: 116 | return 0 117 | else: 118 | return -x 119 | >>> absolute_value(-2) == abs(-2) 120 | True 121 | ``` 122 | 123 | This implementation of `absolute_value` raises several important issues. 124 | 125 | **Conditional statements**. A conditional statement in Python consist of a series of headers and suites: a required `if` clause, an optional sequence of `elif` clauses, and finally an optional `else` clause: 126 | 127 | ``` 128 | if : 129 | 130 | elif : 131 | 132 | else: 133 | 134 | ``` 135 | 136 | When executing a conditional statement, each clause is considered in order. 137 | 138 | 1. Evaluate the header's expression. 139 | 2. If it is a true value, execute the suite. Then, skip over all subsequent clauses in the conditional statement. 140 | 141 | If the `else` clause is reached (which only happens if all `if` and `elif` expressions evaluate to false values), its suite is executed. 142 | 143 | **Boolean contexts**. Above, the execution procedures mention "a false value" and "a true value." The expressions inside the header statements of conditional blocks are said to be in *boolean contexts*: their truth values matter to control flow, but otherwise their values can never be assigned or returned. Python includes several false values, including 0, `None`, and the *boolean* value `False`. All other numbers are true values. In Chapter 2, we will see that every native data type in Python has both true and false values. 144 | 145 | **Boolean values**. Python has two boolean values, called `True` and `False`. Boolean values represent truth values in logical expressions. The built-in comparison operations, `>, <, >=, <=, ==, !=`, return these values. 146 | 147 | ``` 148 | >>> 4 < 2 149 | False 150 | >>> 5 >= 5 151 | True 152 | ``` 153 | 154 | This second example reads "5 is greater than or equal to 5", and corresponds to the function `ge` in the `operator` module. 155 | 156 | ``` 157 | >>> 0 == -0 158 | True 159 | ``` 160 | 161 | This final example reads "0 equals -0", and corresponds to `eq` in the `operator` module. Notice that Python distinguishes assignment (`=`) from equality testing (`==`), a convention shared across many programming languages. 162 | 163 | **Boolean operators**. Three basic logical operators are also built into Python: 164 | 165 | ``` 166 | >>> True and False 167 | False 168 | >>> True or False 169 | True 170 | >>> not False 171 | True 172 | ``` 173 | 174 | Logical expressions have corresponding evaluation procedures. These procedures exploit the fact that the truth value of a logical expression can sometimes be determined without evaluating all of its subexpressions, a feature called *short-circuiting*. 175 | 176 | To evaluate the expression ` and `: 177 | 178 | 1. Evaluate the subexpression ``. 179 | 2. If the result is a false value `v`, then the expression evaluates to `v`. 180 | 3. Otherwise, the expression evaluates to the value of the subexpression ``. 181 | 182 | To evaluate the expression ` or `: 183 | 184 | 1. Evaluate the subexpression ``. 185 | 2. If the result is a true value `v`, then the expression evaluates to `v`. 186 | 3. Otherwise, the expression evaluates to the value of the subexpression ``. 187 | 188 | To evaluate the expression `not `: 189 | 190 | 1. Evaluate ``; The value is `True` if the result is a false value, and `False` otherwise. 191 | 192 | These values, rules, and operators provide us with a way to combine the results of tests. Functions that perform tests and return boolean values typically begin with `is`, not followed by an underscore (e.g., `isfinite`, `isdigit`, `isinstance`, etc.). 193 | 194 | ### 1.5.5 Iteration 195 | 196 | In addition to selecting which statements to execute, control statements are used to express repetition. If each line of code we wrote were only executed once, programming would be a very unproductive exercise. Only through repeated execution of statements do we unlock the potential of computers to make us powerful. We have already seen one form of repetition: a function can be applied many times, although it is only defined once. Iterative control structures are another mechanism for executing the same statements many times. 197 | 198 | Consider the sequence of Fibonacci numbers, in which each number is the sum of the preceding two: 199 | 200 | ``` 201 | 0, 1, 1, 2, 3, 5, 8, 13, 21, ... 202 | ``` 203 | 204 | Each value is constructed by repeatedly applying the sum-previous-two rule. To build up the nth value, we need to track how many values we've created (`k`), along with the kth value (`curr`) and its predecessor (`pred`), like so: 205 | 206 | ``` 207 | >>> def fib(n): 208 | """Compute the nth Fibonacci number, for n >= 2.""" 209 | pred, curr = 0, 1 # Fibonacci numbers 210 | k = 2 # Position of curr in the sequence 211 | while k < n: 212 | pred, curr = curr, pred + curr # Re-bind pred and curr 213 | k = k + 1 # Re-bind k 214 | return curr 215 | >>> fib(8) 216 | 13 217 | ``` 218 | 219 | Remember that commas seperate multiple names and values in an assignment statement. The line: 220 | 221 | ``` 222 | pred, curr = curr, pred + curr 223 | ``` 224 | 225 | has the effect of rebinding the name `pred` to the value of `curr`, and simultanously rebinding `curr` to the value of `pred + curr`. All of the expressions to the right of `=` are evaluated before any rebinding takes place. 226 | 227 | A `while` clause contains a header expression followed by a suite: 228 | 229 | ``` 230 | while : 231 | 232 | ``` 233 | 234 | To execute a `while` clause: 235 | 236 | 1. Evaluate the header's expression. 237 | 2. If it is a true value, execute the suite, then return to step 1. 238 | 239 | In step 2, the entire suite of the `while` clause is executed before the header expression is evaluated again. 240 | 241 | In order to prevent the suite of a `while` clause from being executed indefinitely, the suite should always change the state of the environment in each pass. 242 | 243 | A `while` statement that does not terminate is called an infinite loop. Press `-C` to force Python to stop looping. 244 | 245 | ### 1.5.6 Practical Guidance: Testing 246 | 247 | Testing a function is the act of verifying that the function's behavior matches expectations. Our language of functions is now sufficiently complex that we need to start testing our implementations. 248 | 249 | A *test* is a mechanism for systematically performing this verification. Tests typically take the form of another function that contains one or more sample calls to the function being tested. The returned value is then verified against an expected result. Unlike most functions, which are meant to be general, tests involve selecting and validating calls with specific argument values. Tests also serve as documentation: they demonstrate how to call a function, and what argument values are appropriate. 250 | 251 | Note that we have also used the word "test" as a technical term for the expression in the header of an `if` or `while` statement. It should be clear from context when we use the word "test" to denote an expression, and when we use it to denote a verification mechanism. 252 | 253 | **Assertions.** Programmers use `assert` statements to verify expectations, such as the output of a function being tested. An `assert` statement has an expression in a boolean context, followed by a quoted line of text (single or double quotes are both fine, but be consistent) that will be displayed if the expression evaluates to a false value. 254 | 255 | ``` 256 | >>> assert fib(8) == 13, 'The 8th Fibonacci number should be 13' 257 | ``` 258 | 259 | When the expression being asserted evaluates to a true value, executing an assert statement has no effect. When it is a false value, `assert` causes an error that halts execution. 260 | 261 | A test function for `fib` should test several arguments, including extreme values of `n`. 262 | 263 | ``` 264 | >>> def fib_test(): 265 | assert fib(2) == 1, 'The 2nd Fibonacci number should be 1' 266 | assert fib(3) == 1, 'The 3nd Fibonacci number should be 1' 267 | assert fib(50) == 7778742049, 'Error at the 50th Fibonacci number' 268 | ``` 269 | 270 | When writing Python in files, rather than directly into the interpreter, tests should be written in the same file or a neighboring file with the suffix `_test.py`. 271 | 272 | **Doctests.** Python provides a convenient method for placing simple tests directly in the docstring of a function. The first line of a docstring should contain a one-line description of the function, followed by a blank line. A detailed description of arguments and behavior may follow. In addition, the docstring may include a sample interactive session that calls the function: 273 | 274 | ``` 275 | >>> def sum_naturals(n): 276 | """Return the sum of the first n natural numbers 277 | 278 | >>> sum_naturals(10) 279 | 55 280 | >>> sum_naturals(100) 281 | 5050 282 | """ 283 | total, k = 0, 1 284 | while k <= n: 285 | total, k = total + k, k + 1 286 | return total 287 | ``` 288 | 289 | Then, the interaction can be verified via the [doctest module](http://docs.python.org/py3k/library/doctest.html). Below, the `globals` function returns a representation of the global environment, which the interpreter needs in order to evaluate expressions. 290 | 291 | ``` 292 | >>> from doctest import run_docstring_examples 293 | >>> run_docstring_examples(sum_naturals, globals()) 294 | ``` 295 | 296 | When writing Python in files, all doctests in a file can be run by starting Python with the doctest command line option: 297 | 298 | ``` 299 | python3 -m doctest 300 | ``` 301 | 302 | The key to effective testing is to write (and run) tests immediately after (or even before) implementing new functions. A test that applies a single function is called a *unit test*. Exhaustive unit testing is a hallmark of good program design. -------------------------------------------------------------------------------- /02-buiding-abstractions-with-objects/2.1-introduction.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 2: Building Abstractions with Objects 3 | 4 | ## 2.1 Introduction 5 | 6 | We concentrated in Chapter 1 on computational processes and on the role of functions in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to form compound functions through composition and control, and how to create functional abstractions by giving names to processes. We also saw that higher-order functions 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. 7 | 8 | This chapter focuses on data. Data allow us to represent and manipulate information about the world using the computational tools we have acquired so far. Programs without data structures may suffice for exploring mathematical properties. But real-world phenomena, such as documents, relationships, cities, and weather patterns, all have complex structure that is best represented using *compound data types*. With structured data, programs can simulate and reason about virtually any domain of human knowledge and experience. Thanks to the explosive growth of the Internet, a vast amount of structured information about the world is freely available to us all online. 9 | 10 | ### 2.1.1 The Object Metaphor 11 | 12 | In the beginning of this course, we distinguished between functions and data: functions performed operations and data were operated upon. When we included function values among our data, we acknowledged that data too can have behavior. Functions could be operated upon like data, but could also be called to perform computation. 13 | 14 | In this course, *objects* will serve as our central programming metaphor for data values that also have behavior. Objects represent information, but also *behave* like the abstract concepts that they represent. The logic of how an object interacts with other objects is bundled along with the information that encodes the object's value. When an object is printed, it knows how to spell itself out in letters and numbers. If an object is composed of parts, it knows how to reveal those parts on demand. Objects are both information and processes, bundled together to represent the properties, interactions, and behaviors of complex things. 15 | 16 | The object metaphor is implemented in Python through specialized object syntax and associated terminology, which we can introduce by example. A date is a kind of simple object. 17 | 18 | ``` 19 | >>> from datetime import date 20 | ``` 21 | 22 | The name `date` is bound to a *class*. A class represents a kind of object. Individual dates are called *instances* of that class, and they can be *constructed* by calling the class as a function on arguments that characterize the instance. 23 | 24 | ``` 25 | >>> today = date(2011, 9, 12) 26 | ``` 27 | 28 | While `today` was constructed from primitive numbers, it behaves like a date. For instance, subtracting it from another date will give a time difference, which we can display as a line of text by calling `str`. 29 | 30 | ``` 31 | >>> str(date(2011, 12, 2) - today) 32 | '81 days, 0:00:00' 33 | ``` 34 | 35 | Objects have *attributes*, which are named values that are part of the object. In Python, we use dot notation to designated an attribute of an object. 36 | 37 | > . 38 | 39 | Above, the `` evaluates to an object, and `` is the name of an attribute for that object. 40 | 41 | Unlike the names that we have considered so far, these attribute names are not available in the general environment. Instead, attribute names are particular to the object instance preceding the dot. 42 | 43 | ``` 44 | >>> today.year 45 | 2011 46 | ``` 47 | 48 | Objects also have *methods*, which are function-valued attributes. Metaphorically, the object "knows" how to carry out those methods. Methods compute their results from both their arguments and their object. For example, The `strftime` method of `today` takes a single argument that specifies how to display a date (e.g., `%A` means that the day of the week should be spelled out in full). 49 | 50 | ``` 51 | >>> today.strftime('%A, %B %d') 52 | 'Monday, September 12' 53 | ``` 54 | 55 | Computing the return value of `strftime` requires two inputs: the string that describes the format of the output and the date information bundled into `today`. Date-specific logic is applied within this method to yield this result. We never stated that the 12th of September, 2011, was a Monday, but knowing one's weekday is part of what it means to be a date. By bundling behavior and information together, this Python object offers us a convincing, self-contained abstraction of a date. 56 | 57 | Dot notation provides another form of combined expression in Python. Dot notation also has a well-defined evaluation procedure. However, developing a precise account of how dot notation is evaluated will have to wait until we introduce the full paradigm of object-oriented programming over the next several sections. 58 | 59 | Even though we haven't described precisely how objects work yet, it is time to start thinking about data as objects now, because in Python every value is an object. 60 | 61 | ### 2.1.2 Native Data Types 62 | 63 | Every object in Python has a *type*. The `type` function allows us to inspect the type of an object. 64 | 65 | ``` 66 | >>> type(today) 67 | 68 | ``` 69 | 70 | So far, the only kinds of objects we have studied are numbers, functions, Booleans, and now dates. We also briefly encountered sets and strings, but we will need to study those in more depth. There are many other kinds of objects --- sounds, images, locations, data connections, etc. --- most of which can be defined by the means of combination and abstraction that we develop in this chapter. Python has only a handful of primitive or *native* data types built into the language. 71 | 72 | Native data types have the following properties: 73 | 74 | 1. There are primitive expressions that evaluate to objects of these types, called *literals*. 75 | 2. There are built-in functions, operators, and methods to manipulate these objects. 76 | 77 | As we have seen, numbers are native; numeric literals evaluate to numbers, and mathematical operators manipulate number objects. 78 | 79 | ``` 80 | >>> 12 + 3000000000000000000000000 81 | 3000000000000000000000012 82 | ``` 83 | 84 | In fact, Python includes three native numeric types: integers (`int`), real numbers (`float`), and complex numbers (`complex`). 85 | 86 | ``` 87 | >>> type(2) 88 | 89 | >>> type(1.5) 90 | 91 | >>> type(1+1j) 92 | 93 | ``` 94 | 95 | The name `float` comes from the way in which real numbers are represented in Python: a "floating point" representation. While the details of how numbers are represented is not a topic for this course, some high-level differences between `int` and `float` objects are important to know. In particular, `int` objects can only represent integers, but they represent them exactly, without any approximation. On the other hand, `float` objects can represent a wide range of fractional numbers, but not all rational numbers are representable. Nonetheless, float objects are often used to represent real and rational numbers approximately, up to some number of significant figures. 96 | 97 | **Further reading.** The following sections introduce more of Python's native data types, focusing on the role they play in creating useful data abstractions. A chapter on [native data types](http://diveintopython3.ep.io/native-datatypes.html) in Dive Into Python 3 gives a pragmatic overview of all Python's native data types and how to use them effectively, including numerous usage examples and practical tips. You needn't read that chapter now, but consider it a valuable reference. -------------------------------------------------------------------------------- /02-buiding-abstractions-with-objects/2.2-data-abstraction.md: -------------------------------------------------------------------------------- 1 | 2 | ## 2.2 Data Abstraction 3 | 4 | As we consider the wide set of things in the world that we would like to represent in our programs, we find that most of them have compound structure. A date has a year, a month, and a day; a geographic position has a latitude and a longitude. To represent positions, we would like our programming language to have the capacity to "glue together" a latitude and longitude to form a pair --- a *compound data* value --- that our programs could manipulate in a way that would be consistent with the fact that we regard a position as a single conceptual unit, which has two parts. 5 | 6 | The use of compound data also enables us to increase the modularity of our programs. If we can manipulate geographic positions directly as objects in their own right, then we can separate the part of our program that deals with values per se from the details of how those values may be represented. The general technique of isolating the parts of a program that deal with how data are represented from the parts of a program that deal with how those data are manipulated is a powerful design methodology called *data abstraction*. Data abstraction makes programs much easier to design, maintain, and modify. 7 | 8 | Data abstraction is similar in character to functional abstraction. When we create a functional abstraction, the details of how a function is implemented can be suppressed, and the particular function itself can be replaced by any other function with the same overall behavior. In other words, we can make an abstraction that separates the way the function is used from the details of how the function is implemented. Analogously, data abstraction is a methodology that enables us to isolate how a compound data object is used from the details of how it is constructed. 9 | 10 | The basic idea of data abstraction is to structure programs so that they operate on abstract data. That is, our programs should use data in such a way as to make as few assumptions about the data as possible. At the same time, a concrete data representation is defined, independently of the programs that use the data. The interface between these two parts of our system will be a set of functions, called selectors and constructors, that implement the abstract data in terms of the concrete representation. To illustrate this technique, we will consider how to design a set of functions for manipulating rational numbers. 11 | 12 | As you read the next few sections, keep in mind that most Python code written today uses very high-level abstract data types that are built into the language, like classes, dictionaries, and lists. Since we're building up an understanding of how these abstractions work, we can't use them yet ourselves. As a consequence, we will write some code that isn't Pythonic --- it's not necessarily the typical way to implement our ideas in the language. What we write is instructive, however, because it demonstrates how these abstractions can be constructed! Remember that computer science isn't just about learning to use programming languages, but also learning how they work. 13 | 14 | ### 2.2.1 Example: Arithmetic on Rational Numbers 15 | 16 | Recall that a rational number is a ratio of integers, and rational numbers constitute an important sub-class of real numbers. A rational number like `1/3` or `17/29` is typically written as: 17 | 18 | ``` 19 | / 20 | ``` 21 | 22 | where both the `` and `` are placeholders for integer values. Both parts are needed to exactly characterize the value of the rational number. 23 | 24 | Rational numbers are important in computer science because they, like integers, can be represented exactly. Irrational numbers (like `pi` or `e` or `sqrt(2)`) are instead approximated using a finite binary expansion. Thus, working with rational numbers should, in principle, allow us to avoid approximation errors in our arithmetic. 25 | 26 | However, as soon as we actually divide the numerator by the denominator, we can be left with a truncated decimal approximation (a `float`). 27 | 28 | ``` 29 | >>> 1/3 30 | 0.3333333333333333 31 | ``` 32 | 33 | and the problems with this approximation appear when we start to conduct tests: 34 | 35 | ``` 36 | >>> 1/3 == 0.333333333333333300000 # Beware of approximations 37 | True 38 | ``` 39 | 40 | How computers approximate real numbers with finite-length decimal expansions is a topic for another class. The important idea here is that by representing rational numbers as ratios of integers, we avoid the approximation problem entirely. Hence, we would like to keep the numerator and denominator separate for the sake of precision, but treat them as a single unit. 41 | 42 | We know from using functional abstractions that we can start programming productively before we have an implementation of some parts of our program. Let us begin by assuming that we already have a way of constructing a rational number from a numerator and a denominator. We also assume that, given a rational number, we have a way of extracting (or selecting) its numerator and its denominator. Let us further assume that the constructor and selectors are available as the following three functions: 43 | 44 | - `make_rat(n, d)` returns the rational number with numerator `n` and denominator `d`. 45 | - `numer(x)` returns the numerator of the rational number `x`. 46 | - `denom(x)` returns the denominator of the rational number `x`. 47 | 48 | We are using here a powerful strategy of synthesis: *wishful thinking*. We haven't yet said how a rational number is represented, or how the functions `numer`, `denom`, and `make_rat` should be implemented. Even so, if we did have these three functions, we could then add, multiply, and test equality of rational numbers by calling them: 49 | 50 | ``` 51 | >>> def add_rat(x, y): 52 | nx, dx = numer(x), denom(x) 53 | ny, dy = numer(y), denom(y) 54 | return make_rat(nx * dy + ny * dx, dx * dy) 55 | >>> def mul_rat(x, y): 56 | return make_rat(numer(x) * numer(y), denom(x) * denom(y)) 57 | >>> def eq_rat(x, y): 58 | return numer(x) * denom(y) == numer(y) * denom(x) 59 | ``` 60 | 61 | Now we have the operations on rational numbers defined in terms of the selector functions `numer` and `denom`, and the constructor function `make_rat`, but we haven't yet defined these functions. What we need is some way to glue together a numerator and a denominator into a unit. 62 | 63 | ### 2.2.2 Tuples 64 | 65 | To enable us to implement the concrete level of our data abstraction, Python provides a compound structure called a `tuple`, which can be constructed by separating values by commas. Although not strictly required, parentheses almost always surround tuples. 66 | 67 | ``` 68 | >>> (1, 2) 69 | (1, 2) 70 | ``` 71 | 72 | The elements of a tuple can be unpacked in two ways. The first way is via our familiar method of multiple assignment. 73 | 74 | ``` 75 | >>> pair = (1, 2) 76 | >>> pair 77 | (1, 2) 78 | >>> x, y = pair 79 | >>> x 80 | 1 81 | >>> y 82 | 2 83 | ``` 84 | 85 | In fact, multiple assignment has been creating and unpacking tuples all along. 86 | 87 | A second method for accessing the elements in a tuple is by the indexing operator, written as square brackets. 88 | 89 | ``` 90 | >>> pair[0] 91 | 1 92 | >>> pair[1] 93 | 2 94 | ``` 95 | 96 | Tuples in Python (and sequences in most other programming languages) are 0-indexed, meaning that the index `0` picks out the first element, index `1` picks out the second, and so on. One intuition that underlies this indexing convention is that the index represents how far an element is offset from the beginning of the tuple. 97 | 98 | The equivalent function for the element selection operator is called `getitem`, and it also uses 0-indexed positions to select elements from a tuple. 99 | 100 | ``` 101 | >>> from operator import getitem 102 | >>> getitem(pair, 0) 103 | 1 104 | ``` 105 | 106 | Tuples are native types, which means that there are built-in Python operators to manipulate them. We'll return to the full properties of tuples shortly. At present, we are only interested in how tuples can serve as the glue that implements abstract data types. 107 | 108 | **Representing Rational Numbers.** Tuples offer a natural way to implement rational numbers as a pair of two integers: a numerator and a denominator. We can implement our constructor and selector functions for rational numbers by manipulating 2-element tuples. 109 | 110 | ``` 111 | >>> def make_rat(n, d): 112 | return (n, d) 113 | >>> def numer(x): 114 | return getitem(x, 0) 115 | >>> def denom(x): 116 | return getitem(x, 1) 117 | ``` 118 | 119 | A function for printing rational numbers completes our implementation of this abstract data type. 120 | 121 | ``` 122 | >>> def str_rat(x): 123 | """Return a string 'n/d' for numerator n and denominator d.""" 124 | return '{0}/{1}'.format(numer(x), denom(x)) 125 | ``` 126 | 127 | Together with the arithmetic operations we defined earlier, we can manipulate rational numbers with the functions we have defined. 128 | 129 | ``` 130 | >>> half = make_rat(1, 2) 131 | >>> str_rat(half) 132 | '1/2' 133 | >>> third = make_rat(1, 3) 134 | >>> str_rat(mul_rat(half, third)) 135 | '1/6' 136 | >>> str_rat(add_rat(third, third)) 137 | '6/9' 138 | ``` 139 | 140 | As the final example shows, our rational-number implementation does not reduce rational numbers to lowest terms. We can remedy this by changing `make_rat`. If we have a function for computing the greatest common denominator of two integers, we can use it to reduce the numerator and the denominator to lowest terms before constructing the pair. As with many useful tools, such a function already exists in the Python Library. 141 | 142 | ``` 143 | >>> from fractions import gcd 144 | >>> def make_rat(n, d): 145 | g = gcd(n, d) 146 | return (n//g, d//g) 147 | ``` 148 | 149 | The double slash operator, `//`, expresses integer division, which rounds down the fractional part of the result of division. Since we know that `g` divides both `n` and `d` evenly, integer division is exact in this case. Now we have 150 | 151 | ``` 152 | >>> str_rat(add_rat(third, third)) 153 | '2/3' 154 | ``` 155 | 156 | as desired. This modification was accomplished by changing the constructor without changing any of the functions that implement the actual arithmetic operations. 157 | 158 | **Further reading.** The `str_rat` implementation above uses *format strings*, which contain placeholders for values. The details of how to use format strings and the `format` method appear in the [formatting strings](http://diveintopython3.ep.io/strings.html#formatting-strings) section of Dive Into Python 3. 159 | 160 | ### 2.2.3 Abstraction Barriers 161 | 162 | Before continuing with more examples of compound data and data abstraction, let us consider some of the issues raised by the rational number example. We defined operations in terms of a constructor `make_rat` and selectors `numer` and `denom`. In general, the underlying idea of data abstraction is to identify for each type of value a basic set of operations in terms of which all manipulations of values of that type will be expressed, and then to use only those operations in manipulating the data. 163 | 164 | We can envision the structure of the rational number system as a series of layers. 165 | 166 | ![](img/02/barriers.png) 167 | 168 | The horizontal lines represent abstraction barriers that isolate different levels of the system. At each level, the barrier separates the functions (above) that use the data abstraction from the functions (below) that implement the data abstraction. Programs that use rational numbers manipulate them solely in terms of the their arithmetic functions: `add_rat`, `mul_rat`, and `eq_rat`. These, in turn, are implemented solely in terms of the constructor and selectors `make_rat`, `numer`, and `denom`, which themselves are implemented in terms of tuples. The details of how tuples are implemented are irrelevant to the rest of the layers as long as tuples enable the implementation of the selectors and constructor. 169 | 170 | At each layer, the functions within the box enforce the abstraction boundary because they are the only functions that depend upon both the representation above them (by their use) and the implementation below them (by their definitions). In this way, abstraction barriers are expressed as sets of functions. 171 | 172 | Abstraction barriers provide many advantages. One advantage is that they makes programs much easier to maintain and to modify. The fewer functions that depend on a particular representation, the fewer changes are required when one wants to change that representation. 173 | 174 | ### 2.2.4 The Properties of Data 175 | 176 | We began the rational-number implementation by implementing arithmetic operations in terms of three unspecified functions: `make_rat`, `numer`, and `denom`. At that point, we could think of the operations as being defined in terms of data objects --- numerators, denominators, and rational numbers --- whose behavior was specified by the latter three functions. 177 | 178 | But what exactly is meant by data? It is not enough to say "whatever is implemented by the given selectors and constructors." We need to guarantee that these functions together specify the right behavior. That is, if we construct a rational number `x` from integers `n` and `d`, then it should be the case that `numer(x)/denom(x)` is equal to `n/d`. 179 | 180 | In general, we can think of an abstract data type as defined by some collection of selectors and constructors, together with some behavior conditions. As long as the behavior conditions are met (such as the division property above), these functions constitute a valid representation of the data type. 181 | 182 | This point of view can be applied to other data types as well, such as the two-element tuple that we used in order to implement rational numbers. We never actually said much about what a tuple was, only that the language supplied operators to create and manipulate tuples. We can now describe the behavior conditions of two-element tuples, also called pairs, that are relevant to the problem of representing rational numbers. 183 | 184 | In order to implement rational numbers, we needed a form of glue for two integers, which had the following behavior: 185 | 186 | - If a pair `p` was constructed from values `x` and `y`, then `getitem_pair(p, 0)` returns `x`, and `getitem_pair(p, 1)` returns `y`. 187 | 188 | We can implement functions `make_pair` and `getitem_pair` that fulfill this description just as well as a tuple. 189 | 190 | ``` 191 | >>> def make_pair(x, y): 192 | """Return a function that behaves like a pair.""" 193 | def dispatch(m): 194 | if m == 0: 195 | return x 196 | elif m == 1: 197 | return y 198 | return dispatch 199 | >>> def getitem_pair(p, i): 200 | """Return the element at index i of pair p.""" 201 | return p(i) 202 | ``` 203 | 204 | With this implementation, we can create and manipulate pairs. 205 | 206 | ``` 207 | >>> p = make_pair(1, 2) 208 | >>> getitem_pair(p, 0) 209 | 1 210 | >>> getitem_pair(p, 1) 211 | 2 212 | ``` 213 | 214 | This use of functions corresponds to nothing like our intuitive notion of what data should be. Nevertheless, these functions suffice to represent compound data in our programs. 215 | 216 | The subtle point to notice is that the value returned by `make_pair` is a function called `dispatch`, which takes an argument `m` and returns either `x` or `y`. Then, `getitem_pair` calls this function to retrieve the appropriate value. We will return to the topic of dispatch functions several times throughout this chapter. 217 | 218 | The point of exhibiting the functional representation of a pair is not that Python actually works this way (tuples are implemented more directly, for efficiency reasons) but that it could work this way. The functional representation, although obscure, is a perfectly adequate way to represent pairs, since it fulfills the only conditions that pairs need to fulfill. This example also demonstrates that the ability to manipulate functions as values automatically provides us the ability to represent compound data. -------------------------------------------------------------------------------- /02-buiding-abstractions-with-objects/2.6-implementing-classes-and-objects.md: -------------------------------------------------------------------------------- 1 | 2 | ## 2.6 Implementing Classes and Objects 3 | 4 | When working in the object-oriented programming paradigm, we use the object metaphor to guide the organization of our programs. Most logic about how to represent and manipulate data is expressed within class declarations. In this section, we see that classes and objects can themselves be represented using just functions and dictionaries. The purpose of implementing an object system in this way is to illustrate that using the object metaphor does not require a special programming language. Programs can be object-oriented, even in programming languages that do not have a built-in object system. 5 | 6 | In order to implement objects, we will abandon dot notation (which does require built-in language support), but create dispatch dictionaries that behave in much the same way as the elements of the built-in object system. We have already seen how to implement message-passing behavior through dispatch dictionaries. To implement an object system in full, we send messages between instances, classes, and base classes, all of which are dictionaries that contain attributes. 7 | 8 | We will not implement the entire Python object system, which includes features that we have not covered in this text (e.g., meta-classes and static methods). We will focus instead on user-defined classes without multiple inheritance and without introspective behavior (such as returning the class of an instance). Our implementation is not meant to follow the precise specification of the Python type system. Instead, it is designed to implement the core functionality that enables the object metaphor. 9 | 10 | ### 2.6.1 Instances 11 | 12 | We begin with instances. An instance has named attributes, such as the balance of an account, which can be set and retrieved. We implement an instance using a dispatch dictionary that responds to messages that "get" and "set" attribute values. Attributes themselves are stored in a local dictionary called `attributes`. 13 | 14 | As we have seen previously in this chapter, dictionaries themselves are abstract data types. We implemented dictionaries with lists, we implemented lists with pairs, and we implemented pairs with functions. As we implement an object system in terms of dictionaries, keep in mind that we could just as well be implementing objects using functions alone. 15 | 16 | To begin our implementation, we assume that we have a class implementation that can look up any names that are not part of the instance. We pass in a class to `make_instance` as the parameter `cls`. 17 | 18 | ``` 19 | >>> def make_instance(cls): 20 | """Return a new object instance, which is a dispatch dictionary.""" 21 | def get_value(name): 22 | if name in attributes: 23 | return attributes[name] 24 | else: 25 | value = cls['get'](name) 26 | return bind_method(value, instance) 27 | def set_value(name, value): 28 | attributes[name] = value 29 | attributes = {} 30 | instance = {'get': get_value, 'set': set_value} 31 | return instance 32 | ``` 33 | 34 | The `instance` is a dispatch dictionary that responds to the messages `get` and `set`. The `set` message corresponds to attribute assignment in Python's object system: all assigned attributes are stored directly within the object's local attribute dictionary. In `get`, if `name` does not appear in the local `attributes` dictionary, then it is looked up in the class. If the `value` returned by `cls` is a function, it must be bound to the instance. 35 | 36 | **Bound method values.** The `get_value` function in `make_instance` finds a named attribute in its class with `get`, then calls `bind_method`. Binding a method only applies to function values, and it creates a bound method value from a function value by inserting the instance as the first argument: 37 | 38 | ``` 39 | >>> def bind_method(value, instance): 40 | """Return a bound method if value is callable, or value otherwise.""" 41 | if callable(value): 42 | def method(*args): 43 | return value(instance, *args) 44 | return method 45 | else: 46 | return value 47 | ``` 48 | 49 | When a method is called, the first parameter `self` will be bound to the value of `instance` by this definition. 50 | 51 | ### 2.6.2 Classes 52 | 53 | A class is also an object, both in Python's object system and the system we are implementing here. For simplicity, we say that classes do not themselves have a class. (In Python, classes do have classes; almost all classes share the same class, called `type`.) A class can respond to `get` and `set` messages, as well as the `new` message: 54 | 55 | ``` 56 | >>> def make_class(attributes, base_class=None): 57 | """Return a new class, which is a dispatch dictionary.""" 58 | def get_value(name): 59 | if name in attributes: 60 | return attributes[name] 61 | elif base_class is not None: 62 | return base_class['get'](name) 63 | def set_value(name, value): 64 | attributes[name] = value 65 | def new(*args): 66 | return init_instance(cls, *args) 67 | cls = {'get': get_value, 'set': set_value, 'new': new} 68 | return cls 69 | ``` 70 | 71 | Unlike an instance, the `get` function for classes does not query its class when an attribute is not found, but instead queries its `base_class`. No method binding is required for classes. 72 | 73 | **Initialization.** The `new` function in `make_class` calls `init_instance`, which first makes a new instance, then invokes a method called `__init__`. 74 | 75 | ``` 76 | >>> def init_instance(cls, *args): 77 | """Return a new object with type cls, initialized with args.""" 78 | instance = make_instance(cls) 79 | init = cls['get']('__init__') 80 | if init: 81 | init(instance, *args) 82 | return instance 83 | ``` 84 | 85 | This final function completes our object system. We now have instances, which `set` locally but fall back to their classes on `get`. After an instance looks up a name in its class, it binds itself to function values to create methods. Finally, classes can create `new` instances, and they apply their `__init__` constructor function immediately after instance creation. 86 | 87 | In this object system, the only function that should be called by the user is `create_class`. All other functionality is enabled through message passing. Similarly, Python's object system is invoked via the `class` statement, and all of its other functionality is enabled through dot expressions and calls to classes. 88 | 89 | ### 2.6.3 Using Implemented Objects 90 | 91 | We now return to use the bank account example from the previous section. Using our implemented object system, we will create an `Account` class, a `CheckingAccount` subclass, and an instance of each. 92 | 93 | The `Account` class is created through a `create_account_class` function, which has structure similar to a `class` statement in Python, but concludes with a call to `make_class`. 94 | 95 | ``` 96 | >>> def make_account_class(): 97 | """Return the Account class, which has deposit and withdraw methods.""" 98 | def __init__(self, account_holder): 99 | self['set']('holder', account_holder) 100 | self['set']('balance', 0) 101 | def deposit(self, amount): 102 | """Increase the account balance by amount and return the new balance.""" 103 | new_balance = self['get']('balance') + amount 104 | self['set']('balance', new_balance) 105 | return self['get']('balance') 106 | def withdraw(self, amount): 107 | """Decrease the account balance by amount and return the new balance.""" 108 | balance = self['get']('balance') 109 | if amount > balance: 110 | return 'Insufficient funds' 111 | self['set']('balance', balance - amount) 112 | return self['get']('balance') 113 | return make_class({'__init__': __init__, 114 | 'deposit': deposit, 115 | 'withdraw': withdraw, 116 | 'interest': 0.02}) 117 | ``` 118 | 119 | In this function, the names of attributes are set at the end. Unlike Python `class` statements, which enforce consistency between intrinsic function names and attribute names, here we must specify the correspondence between attribute names and values manually. 120 | 121 | The `Account` class is finally instantiated via assignment. 122 | 123 | ``` 124 | >>> Account = make_account_class() 125 | ``` 126 | 127 | Then, an account instance is created via the `new` message, which requires a name to go with the newly created account. 128 | 129 | ``` 130 | >>> jim_acct = Account['new']('Jim') 131 | ``` 132 | 133 | Then, `get` messages passed to `jim_acct` retrieve properties and methods. Methods can be called to update the balance of the account. 134 | 135 | ``` 136 | >>> jim_acct['get']('holder') 137 | 'Jim' 138 | >>> jim_acct['get']('interest') 139 | 0.02 140 | >>> jim_acct['get']('deposit')(20) 141 | 20 142 | >>> jim_acct['get']('withdraw')(5) 143 | 15 144 | ``` 145 | 146 | As with the Python object system, setting an attribute of an instance does not change the corresponding attribute of its class. 147 | 148 | ``` 149 | >>> jim_acct['set']('interest', 0.04) 150 | >>> Account['get']('interest') 151 | 0.02 152 | ``` 153 | 154 | **Inheritance.** We can create a subclass `CheckingAccount` by overloading a subset of the class attributes. In this case, we change the `withdraw` method to impose a fee, and we reduce the interest rate. 155 | 156 | ``` 157 | >>> def make_checking_account_class(): 158 | """Return the CheckingAccount class, which imposes a $1 withdrawal fee.""" 159 | def withdraw(self, amount): 160 | return Account['get']('withdraw')(self, amount + 1) 161 | return make_class({'withdraw': withdraw, 'interest': 0.01}, Account) 162 | ``` 163 | 164 | In this implementation, we call the `withdraw` function of the base class `Account` from the `withdraw` function of the subclass, as we would in Python's built-in object system. We can create the subclass itself and an instance, as before. 165 | 166 | ``` 167 | >>> CheckingAccount = make_checking_account_class() 168 | >>> jack_acct = CheckingAccount['new']('Jack') 169 | ``` 170 | 171 | Deposits behave identically, as does the constructor function. withdrawals impose the $1 fee from the specialized `withdraw` method, and `interest` has the new lower value from `CheckingAccount`. 172 | 173 | ``` 174 | >>> jack_acct['get']('interest') 175 | 0.01 176 | >>> jack_acct['get']('deposit')(20) 177 | 20 178 | >>> jack_acct['get']('withdraw')(5) 179 | 14 180 | ``` 181 | 182 | Our object system built upon dictionaries is quite similar in implementation to the built-in object system in Python. In Python, an instance of any user-defined class has a special attribute `__dict__` that stores the local instance attributes for that object in a dictionary, much like our `attributes` dictionary. Python differs because it distinguishes certain special methods that interact with built-in functions to ensure that those functions behave correctly for arguments of many different types. Functions that operate on different types are the subject of the next section. -------------------------------------------------------------------------------- /03-the-structure-and-interpretation-of-computer-programs/3.1-introduction.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 3: The Structure and Interpretation of Computer Programs 3 | 4 | ## 3.1 Introduction 5 | 6 | Chapters 1 and 2 describe the close connection between two fundamental elements of programming: functions and data. We saw how functions can be manipulated as data using higher-order functions. We also saw how data can be endowed with behavior using message passing and an object system. We have also studied techniques for organizing large programs, such as functional abstraction, data abstraction, class inheritance, and generic functions. These core concepts constitute a strong foundation upon which to build modular, maintainable, and extensible programs. 7 | 8 | This chapter focuses on the third fundamental element of programming: programs themselves. A Python program is just a collection of text. Only through the process of interpretation do we perform any meaningful computation based on that text. A programming language like Python is useful because we can define an *interpreter*, a program that carries out Python's evaluation and execution procedures. It is no exaggeration to regard this as the most fundamental idea in programming, that an interpreter, which determines the meaning of expressions in a programming language, is just another program. 9 | 10 | 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. 11 | 12 | ### 3.1.1 Programming Languages 13 | 14 | In fact, we can regard many programs as interpreters for some language. For example, the constraint propagator from the previous chapter has its own primitives and means of combination. The constraint language was quite specialized: it provided a declarative method for describing a certain class of mathematical relations, not a fully general language for describing computation. While we have been designing languages of a sort already, the material of this chapter will greatly expand the range of languages we can interpret. 15 | 16 | Programming languages vary widely in their syntactic structures, features, and domain of application. Among general purpose programming languages, the constructs of function definition and function application are pervasive. On the other hand, powerful languages exist that do not include an object system, higher-order functions, or even control constructs like `while` and `for` statements. To illustrate just how different languages can be, we will introduce [Logo](http://www.cs.berkeley.edu/~bh/logo.html) as an example of a powerful and expressive programming language that includes very few advanced features. 17 | 18 | In this chapter, we study the design of interpreters and the computational processes that they create when executing programs. The prospect of designing an interpreter for a general programming language may seem daunting. After all, interpreters are programs that can carry out any possible computation, depending on their input. However, typical interpreters have an elegant common structure: two mutually recursive functions. The first evaluates expressions in environments; the second applies functions to arguments. 19 | 20 | These functions are *recursive* in that they are defined in terms of each other: applying a function requires evaluating the expressions in its body, while evaluating an expression may involve applying one or more functions. The next two sections of this chapter focus on recursive functions and data structures, which will prove essential to understanding the design of an interpreter. The end of the chapter focuses on two new languages and the task of implementing interpreters for them. -------------------------------------------------------------------------------- /03-the-structure-and-interpretation-of-computer-programs/3.3-recursive-data-structures.md: -------------------------------------------------------------------------------- 1 | 2 | ## 3.3 Recursive Data Structures 3 | 4 | In Chapter 2, we introduced the notion of a pair as a primitive mechanism for glueing together two objects into one. We showed that a pair can be implemented using a built-in tuple. The *closure* property of pairs indicated that either element of a pair could itself be a pair. 5 | 6 | This closure property allowed us to implement the recursive list data abstraction, which served as our first type of sequence. Recursive lists are most naturally manipulated using recursive functions, as their name and structure would suggest. In this section, we discuss functions for creating and manipulating recursive lists and other recursive data structures. 7 | 8 | ### 3.3.1 Processing Recursive Lists 9 | 10 | Recall that the recursive list abstract data type represented a list as a first element and the rest of the list. We previously implemented recursive lists using functions, but at this point we can re-implement them using a class. Below, the length (`__len__`) and element selection (`__getitem__`) functions are written recursively to demonstrate typical patterns for processing recursive lists. 11 | 12 | ``` 13 | >>> class Rlist(object): 14 | """A recursive list consisting of a first element and the rest.""" 15 | class EmptyList(object): 16 | def __len__(self): 17 | return 0 18 | empty = EmptyList() 19 | def __init__(self, first, rest=empty): 20 | self.first = first 21 | self.rest = rest 22 | def __repr__(self): 23 | args = repr(self.first) 24 | if self.rest is not Rlist.empty: 25 | args += ', {0}'.format(repr(self.rest)) 26 | return 'Rlist({0})'.format(args) 27 | def __len__(self): 28 | return 1 + len(self.rest) 29 | def __getitem__(self, i): 30 | if i == 0: 31 | return self.first 32 | return self.rest[i-1] 33 | ``` 34 | 35 | The definitions of `__len__` and `__getitem__` are in fact recursive, although not explicitly so. The built-in Python function `len` looks for a method called `__len__` when applied to a user-defined object argument. Likewise, the subscript operator looks for a method called `__getitem__`. Thus, these definitions will end up calling themselves. Recursive calls on the rest of the list are a ubiquitous pattern in recursive list processing. This class definition of a recursive list interacts properly with Python's built-in sequence and printing operations. 36 | 37 | ``` 38 | >>> s = Rlist(1, Rlist(2, Rlist(3))) 39 | >>> s.rest 40 | Rlist(2, Rlist(3)) 41 | >>> len(s) 42 | 3 43 | >>> s[1] 44 | 2 45 | ``` 46 | 47 | Operations that create new lists are particularly straightforward to express using recursion. For example, we can define a function `extend_rlist`, which takes two recursive lists as arguments and combines the elements of both into a new list. 48 | 49 | ``` 50 | >>> def extend_rlist(s1, s2): 51 | if s1 is Rlist.empty: 52 | return s2 53 | return Rlist(s1.first, extend_rlist(s1.rest, s2)) 54 | >>> extend_rlist(s.rest, s) 55 | Rlist(2, Rlist(3, Rlist(1, Rlist(2, Rlist(3))))) 56 | ``` 57 | 58 | Likewise, mapping a function over a recursive list exhibits a similar pattern. 59 | 60 | ``` 61 | >>> def map_rlist(s, fn): 62 | if s is Rlist.empty: 63 | return s 64 | return Rlist(fn(s.first), map_rlist(s.rest, fn)) 65 | >>> map_rlist(s, square) 66 | Rlist(1, Rlist(4, Rlist(9))) 67 | ``` 68 | 69 | Filtering includes an additional conditional statement, but otherwise has a similar recursive structure. 70 | 71 | ``` 72 | >>> def filter_rlist(s, fn): 73 | if s is Rlist.empty: 74 | return s 75 | rest = filter_rlist(s.rest, fn) 76 | if fn(s.first): 77 | return Rlist(s.first, rest) 78 | return rest 79 | >>> filter_rlist(s, lambda x: x % 2 == 1) 80 | Rlist(1, Rlist(3)) 81 | ``` 82 | 83 | Recursive implementations of list operations do not, in general, require local assignment or `while` statements. Instead, recursive lists are taken apart and constructed incrementally as a consequence of function application. As a result, they have linear orders of growth in both the number of steps and space required. 84 | 85 | ### 3.3.2 Hierarchical Structures 86 | 87 | Hierarchical structures result from the closure property of data, which asserts for example that tuples can contain other tuples. For instance, consider this nested representation of the numbers 1 through 4. 88 | 89 | ``` 90 | >>> ((1, 2), 3, 4) 91 | ((1, 2), 3, 4) 92 | ``` 93 | 94 | This tuple is a length-three sequence, of which the first element is itself a tuple. A box-and-pointer diagram of this nested structure shows that it can also be thought of as a tree with four leaves, each of which is a number. 95 | 96 | ![](img/03/tree.png) 97 | 98 | In a tree, each subtree is itself a tree. As a base condition, any bare element that is not a tuple is itself a simple tree, one with no branches. That is, the numbers are all trees, as is the pair `(1, 2)` and the structure as a whole. 99 | 100 | Recursion is a natural tool for dealing with tree structures, since we can often reduce operations on trees to operations on their branches, which reduce in turn to operations on the branches of the branches, and so on, until we reach the leaves of the tree. As an example, we can implement a `count_leaves` function, which returns the total number of leaves of a tree. 101 | 102 | ``` 103 | >>> def count_leaves(tree): 104 | if type(tree) != tuple: 105 | return 1 106 | return sum(map(count_leaves, tree)) 107 | >>> t = ((1, 2), 3, 4) 108 | >>> count_leaves(t) 109 | 4 110 | >>> big_tree = ((t, t), 5) 111 | >>> big_tree 112 | ((((1, 2), 3, 4), ((1, 2), 3, 4)), 5) 113 | >>> count_leaves(big_tree) 114 | 9 115 | ``` 116 | 117 | Just as `map` is a powerful tool for dealing with sequences, mapping and recursion together provide a powerful general form of computation for manipulating trees. For instance, we can square all leaves of a tree using a higher-order recursive function `map_tree` that is structured quite similarly to `count_leaves`. 118 | 119 | ``` 120 | >>> def map_tree(tree, fn): 121 | if type(tree) != tuple: 122 | return fn(tree) 123 | return tuple(map_tree(branch, fn) for branch in tree) 124 | >>> map_tree(big_tree, square) 125 | ((((1, 4), 9, 16), ((1, 4), 9, 16)), 25) 126 | ``` 127 | 128 | **Internal values.** The trees described above have values only at the leaves. Another common representation of tree-structured data has values for the internal nodes of the tree as well. We can represent such trees using a class. 129 | 130 | ``` 131 | >>> class Tree(object): 132 | def __init__(self, entry, left=None, right=None): 133 | self.entry = entry 134 | self.left = left 135 | self.right = right 136 | def __repr__(self): 137 | args = repr(self.entry) 138 | if self.left or self.right: 139 | args += ', {0}, {1}'.format(repr(self.left), repr(self.right)) 140 | return 'Tree({0})'.format(args) 141 | ``` 142 | 143 | The `Tree` class can represent, for instance, the values computed in an expression tree for the recursive implementation of `fib`, the function for computing Fibonacci numbers. The function `fib_tree(n)` below returns a `Tree` that has the nth Fibonacci number as its `entry` and a trace of all previously computed Fibonacci numbers within its branches. 144 | 145 | ``` 146 | >>> def fib_tree(n): 147 | """Return a Tree that represents a recursive Fibonacci calculation.""" 148 | if n == 1: 149 | return Tree(0) 150 | if n == 2: 151 | return Tree(1) 152 | left = fib_tree(n-2) 153 | right = fib_tree(n-1) 154 | return Tree(left.entry + right.entry, left, right) 155 | >>> fib_tree(5) 156 | Tree(3, Tree(1, Tree(0), Tree(1)), Tree(2, Tree(1), Tree(1, Tree(0), Tree(1)))) 157 | ``` 158 | 159 | This example shows that expression trees can be represented programmatically using tree-structured data. This connection between nested expressions and tree-structured data type plays a central role in our discussion of designing interpreters later in this chapter. 160 | 161 | ### 3.3.3 Sets 162 | 163 | In addition to the list, tuple, and dictionary, Python has a fourth built-in container type called a `set`. Set literals follow the mathematical notation of elements enclosed in braces. Duplicate elements are removed upon construction. Sets are unordered collections, and so the printed ordering may differ from the element ordering in the set literal. 164 | 165 | ``` 166 | >>> s = {3, 2, 1, 4, 4} 167 | >>> s 168 | {1, 2, 3, 4} 169 | ``` 170 | 171 | Python sets support a variety of operations, including membership tests, length computation, and the standard set operations of union and intersection 172 | 173 | ``` 174 | >>> 3 in s 175 | True 176 | >>> len(s) 177 | 4 178 | >>> s.union({1, 5}) 179 | {1, 2, 3, 4, 5} 180 | >>> s.intersection({6, 5, 4, 3}) 181 | {3, 4} 182 | ``` 183 | 184 | In addition to `union` and `intersection`, Python sets support several other methods. The predicates `isdisjoint`, `issubset`, and `issuperset` provide set comparison. Sets are mutable, and can be changed one element at a time using `add`, `remove`, `discard`, and `pop`. Additional methods provide multi-element mutations, such as `clear` and `update`. The Python [documentation for sets](http://docs.python.org/py3k/library/stdtypes.html#set) should be sufficiently intelligible at this point of the course to fill in the details. 185 | 186 | **Implementing sets.** Abstractly, a set is a collection of distinct objects that supports membership testing, union, intersection, and adjunction. Adjoining an element and a set returns a new set that contains all of the original set's elements along with the new element, if it is distinct. Union and intersection return the set of elements that appear in either or both sets, respectively. As with any data abstraction, we are free to implement any functions over any representation of sets that provides this collection of behaviors. 187 | 188 | In the remainder of this section, we consider three different methods of implementing sets that vary in their representation. We will characterize the efficiency of these different representations by analyzing the order of growth of set operations. We will use our `Rlist` and `Tree` classes from earlier in this section, which allow for simple and elegant recursive solutions for elementary set operations. 189 | 190 | **Sets as unordered sequences.** One way to represent a set is as a sequence in which no element appears more than once. The empty set is represented by the empty sequence. Membership testing walks recursively through the list. 191 | 192 | ``` 193 | >>> def empty(s): 194 | return s is Rlist.empty 195 | >>> def set_contains(s, v): 196 | """Return True if and only if set s contains v.""" 197 | if empty(s): 198 | return False 199 | elif s.first == v: 200 | return True 201 | return set_contains(s.rest, v) 202 | >>> s = Rlist(1, Rlist(2, Rlist(3))) 203 | >>> set_contains(s, 2) 204 | True 205 | >>> set_contains(s, 5) 206 | False 207 | ``` 208 | 209 | This implementation of `set_contains` requires (\Theta(n)) time to test membership of an element, where (n) is the size of the set `s`. Using this linear-time function for membership, we can adjoin an element to a set, also in linear time. 210 | 211 | ``` 212 | >>> def adjoin_set(s, v): 213 | """Return a set containing all elements of s and element v.""" 214 | if set_contains(s, v): 215 | return s 216 | return Rlist(v, s) 217 | >>> t = adjoin_set(s, 4) 218 | >>> t 219 | Rlist(4, Rlist(1, Rlist(2, Rlist(3)))) 220 | ``` 221 | 222 | In designing a representation, one of the issues with which we should be concerned is efficiency. Intersecting two sets `set1` and `set2` also requires membership testing, but this time each element of `set1` must be tested for membership in `set2`, leading to a quadratic order of growth in the number of steps, (\Theta(n^2)), for two sets of size (n). 223 | 224 | ``` 225 | >>> def intersect_set(set1, set2): 226 | """Return a set containing all elements common to set1 and set2.""" 227 | return filter_rlist(set1, lambda v: set_contains(set2, v)) 228 | >>> intersect_set(t, map_rlist(s, square)) 229 | Rlist(4, Rlist(1)) 230 | ``` 231 | 232 | When computing the union of two sets, we must be careful not to include any element twice. The `union_set` function also requires a linear number of membership tests, creating a process that also includes (\Theta(n^2)) steps. 233 | 234 | ``` 235 | >>> def union_set(set1, set2): 236 | """Return a set containing all elements either in set1 or set2.""" 237 | set1_not_set2 = filter_rlist(set1, lambda v: not set_contains(set2, v)) 238 | return extend_rlist(set1_not_set2, set2) 239 | >>> union_set(t, s) 240 | Rlist(4, Rlist(1, Rlist(2, Rlist(3)))) 241 | ``` 242 | 243 | **Sets as ordered tuples.** One way to speed up our set operations is to change the representation so that the set elements are listed in increasing order. To do this, we need some way to compare two objects so that we can say which is bigger. In Python, many different types of objects can be compared using `<` and `>` operators, but we will concentrate on numbers in this example. We will represent a set of numbers by listing its elements in increasing order. 244 | 245 | One advantage of ordering shows up in `set_contains`: In checking for the presence of an object, we no longer have to scan the entire set. If we reach a set element that is larger than the item we are looking for, then we know that the item is not in the set: 246 | 247 | ``` 248 | >>> def set_contains(s, v): 249 | if empty(s) or s.first > v: 250 | return False 251 | elif s.first == v: 252 | return True 253 | return set_contains(s.rest, v) 254 | >>> set_contains(s, 0) 255 | False 256 | ``` 257 | 258 | How many steps does this save? In the worst case, the item we are looking for may be the largest one in the set, so the number of steps is the same as for the unordered representation. On the other hand, if we search for items of many different sizes we can expect that sometimes we will be able to stop searching at a point near the beginning of the list and that other times we will still need to examine most of the list. On average we should expect to have to examine about half of the items in the set. Thus, the average number of steps required will be about (\frac{n}{2}). This is still (\Theta(n)) growth, but it does save us, on average, a factor of `2` in the number of steps over the previous implementation. 259 | 260 | We can obtain a more impressive speedup by re-implementing `intersect_set`. In the unordered representation, this operation required (\Theta(n^2)) steps because we performed a complete scan of `set2` for each element of `set1`. But with the ordered representation, we can use a more clever method. We iterate through both sets simultaneously, tracking an element `e1` in `set1` and `e2` in `set2`. When `e1` and `e2` are equal, we include that element in the intersection. 261 | 262 | Suppose, however, that `e1` is less than `e2`. Since `e2` is smaller than the remaining elements of `set2`, we can immediately conclude that `e1` cannot appear anywhere in the remainder of `set2` and hence is not in the intersection. Thus, we no longer need to consider `e1`; we discard it and proceed to the next element of `set1`. Similar logic advances through the elements of `set2` when `e2 < e1`. Here is the function: 263 | 264 | ``` 265 | >>> def intersect_set(set1, set2): 266 | if empty(set1) or empty(set2): 267 | return Rlist.empty 268 | e1, e2 = set1.first, set2.first 269 | if e1 == e2: 270 | return Rlist(e1, intersect_set(set1.rest, set2.rest)) 271 | elif e1 < e2: 272 | return intersect_set(set1.rest, set2) 273 | elif e2 < e1: 274 | return intersect_set(set1, set2.rest) 275 | >>> intersect_set(s, s.rest) 276 | Rlist(2, Rlist(3)) 277 | ``` 278 | 279 | To estimate the number of steps required by this process, observe that in each step we shrink the size of at least one of the sets. Thus, the number of steps required is at most the sum of the sizes of `set1` and `set2`, rather than the product of the sizes, as with the unordered representation. This is (\Theta(n)) growth rather than (\Theta(n^2)) -- a considerable speedup, even for sets of moderate size. For example, the intersection of two sets of size `100` will take around `200` steps, rather than `10,000` for the unordered representation. 280 | 281 | Adjunction and union for sets represented as ordered sequences can also be computed in linear time. These implementations are left as an exercise. 282 | 283 | **Sets as binary trees.** We can do better than the ordered-list representation by arranging the set elements in the form of a tree. We use the `Tree` class introduced previously. The `entry` of the root of the tree holds one element of the set. The entries within the `left` branch include all elements smaller than the one at the root. Entries in the `right` branch include all elements greater than the one at the root. The figure below shows some trees that represent the set `{1, 3, 5, 7, 9, 11}`. The same set may be represented by a tree in a number of different ways. The only thing we require for a valid representation is that all elements in the `left` subtree be smaller than the tree `entry` and that all elements in the `right` subtree be larger. 284 | 285 | ![](img/03/set_trees.png) 286 | 287 | The advantage of the tree representation is this: Suppose we want to check whether a value `v` is contained in a set. We begin by comparing `v` with `entry`. If `v` is less than this, we know that we need only search the `left` subtree; if `v` is greater, we need only search the `right` subtree. Now, if the tree is "balanced," each of these subtrees will be about half the size of the original. Thus, in one step we have reduced the problem of searching a tree of size (n) to searching a tree of size (\frac{n}{2}). Since the size of the tree is halved at each step, we should expect that the number of steps needed to search a tree grows as (\Theta(\log n)). For large sets, this will be a significant speedup over the previous representations. This `set_contains` function exploits the ordering structure of the tree-structured set. 288 | 289 | ``` 290 | >>> def set_contains(s, v): 291 | if s is None: 292 | return False 293 | elif s.entry == v: 294 | return True 295 | elif s.entry < v: 296 | return set_contains(s.right, v) 297 | elif s.entry > v: 298 | return set_contains(s.left, v) 299 | ``` 300 | 301 | Adjoining an item to a set is implemented similarly and also requires (\Theta(\log n)) steps. To adjoin a value `v`, we compare `v` with `entry` to determine whether `v` should be added to the `right` or to the `left` branch, and having adjoined `v` to the appropriate branch we piece this newly constructed branch together with the original `entry` and the other branch. If `v` is equal to the `entry`, we just return the node. If we are asked to adjoin `v` to an empty tree, we generate a `Tree` that has `v` as the `entry` and empty `right` and `left` branches. Here is the function: 302 | 303 | ``` 304 | >>> def adjoin_set(s, v): 305 | if s is None: 306 | return Tree(v) 307 | if s.entry == v: 308 | return s 309 | if s.entry < v: 310 | return Tree(s.entry, s.left, adjoin_set(s.right, v)) 311 | if s.entry > v: 312 | return Tree(s.entry, adjoin_set(s.left, v), s.right) 313 | >>> adjoin_set(adjoin_set(adjoin_set(None, 2), 3), 1) 314 | Tree(2, Tree(1), Tree(3)) 315 | ``` 316 | 317 | Our claim that searching the tree can be performed in a logarithmic number of steps rests on the assumption that the tree is "balanced," i.e., that the left and the right subtree of every tree have approximately the same number of elements, so that each subtree contains about half the elements of its parent. But how can we be certain that the trees we construct will be balanced? Even if we start with a balanced tree, adding elements with `adjoin_set` may produce an unbalanced result. Since the position of a newly adjoined element depends on how the element compares with the items already in the set, we can expect that if we add elements "randomly" the tree will tend to be balanced on the average. 318 | 319 | But this is not a guarantee. For example, if we start with an empty set and adjoin the numbers 1 through 7 in sequence we end up with a highly unbalanced tree in which all the left subtrees are empty, so it has no advantage over a simple ordered list. One way to solve this problem is to define an operation that transforms an arbitrary tree into a balanced tree with the same elements. We can perform this transformation after every few `adjoin_set` operations to keep our set in balance. 320 | 321 | Intersection and union operations can be performed on tree-structured sets in linear time by converting them to ordered lists and back. The details are left as an exercise. 322 | 323 | **Python set implementation.** The `set` type that is built into Python does not use any of these representations internally. Instead, Python uses a representation that gives constant-time membership tests and adjoin operations based on a technique called *hashing*, which is a topic for another course. Built-in Python sets cannot contain mutable data types, such as lists, dictionaries, or other sets. To allow for nested sets, Python also includes a built-in immutable `frozenset` class that shares methods with the `set` class but excludes mutation methods and operators. -------------------------------------------------------------------------------- /03-the-structure-and-interpretation-of-computer-programs/3.4-exceptions.md: -------------------------------------------------------------------------------- 1 | 2 | ## 3.4 Exceptions 3 | 4 | Programmers must be always mindful of possible errors that may arise in their programs. Examples abound: a function may not receive arguments that it is designed to accept, a necessary resource may be missing, or a connection across a network may be lost. When designing a program, one must anticipate the exceptional circumstances that may arise and take appropriate measures to handle them. 5 | 6 | There is no single correct approach to handling errors in a program. Programs designed to provide some persistent service like a web server should be robust to errors, logging them for later consideration but continuing to service new requests as long as possible. On the other hand, the Python interpreter handles errors by terminating immediately and printing an error message, so that programmers can address issues as soon as they arise. In any case, programmers must make conscious choices about how their programs should react to exceptional conditions. 7 | 8 | *Exceptions*, the topic of this section, provides a general mechanism for adding error-handling logic to programs. *Raising an exception* is a technique for interrupting the normal flow of execution in a program, signaling that some exceptional circumstance has arisen, and returning directly to an enclosing part of the program that was designated to react to that circumstance. The Python interpreter raises an exception each time it detects an error in an expression or statement. Users can also raise exceptions with `raise` and `assert` statements. 9 | 10 | **Raising exceptions.** An exception is a object instance with a class that inherits, either directly or indirectly, from the `BaseException` class. The `assert` statement introduced in Chapter 1 raises an exception with the class `AssertionError`. In general, any exception instance can be raised with the `raise` statement. The general form of raise statements are described in the [Python docs](http://docs.python.org/py3k/reference/simple_stmts.html#raise). The most common use of `raise` constructs an exception instance and raises it. 11 | 12 | ``` 13 | >>> raise Exception('An error occurred') 14 | Traceback (most recent call last): 15 | File "", line 1, in 16 | Exception: an error occurred 17 | ``` 18 | 19 | When an exception is raised, no further statements in the current block of code are executed. Unless the exception is *handled* (described below), the interpreter will return directly to the interactive read-eval-print loop, or terminate entirely if Python was started with a file argument. In addition, the interpreter will print a *stack backtrace*, which is a structured block of text that describes the nested set of active function calls in the branch of execution in which the exception was raised. In the example above, the file name `` indicates that the exception was raised by the user in an interactive session, rather than from code in a file. 20 | 21 | **Handling exceptions.** An exception can be handled by an enclosing `try` statement. A `try` statement consists of multiple clauses; the first begins with `try` and the rest begin with `except`: 22 | 23 | ``` 24 | try: 25 | 26 | except as : 27 | 28 | ... 29 | ``` 30 | 31 | The `` is always executed immediately when the `try` statement is executed. Suites of the `except` clauses are only executed when an exception is raised during the course of executing the ``. Each `except` clause specifies the particular class of exception to handle. For instance, if the `` is `AssertionError`, then any instance of a class inheriting from `AssertionError` that is raised during the course of executing the `` will be handled by the following ``. Within the ``, the identifier `` is bound to the exception object that was raised, but this binding does not persist beyond the ``. 32 | 33 | For example, we can handle a `ZeroDivisionError` exception using a `try` statement that binds the name `x` to `0` when the exception is raised. 34 | 35 | ``` 36 | >>> try: 37 | x = 1/0 38 | except ZeroDivisionError as e: 39 | print('handling a', type(e)) 40 | x = 0 41 | handling a 42 | >>> x 43 | 0 44 | ``` 45 | 46 | A `try` statement will handle exceptions that occur within the body of a function that is applied (either directly or indirectly) within the ``. When an exception is raised, control jumps directly to the body of the `` of the most recent `try` statement that handles that type of exception. 47 | 48 | ``` 49 | >>> def invert(x): 50 | result = 1/x # Raises a ZeroDivisionError if x is 0 51 | print('Never printed if x is 0') 52 | return result 53 | >>> def invert_safe(x): 54 | try: 55 | return invert(x) 56 | except ZeroDivisionError as e: 57 | return str(e) 58 | >>> invert_safe(2) 59 | Never printed if x is 0 60 | 0.5 61 | >>> invert_safe(0) 62 | 'division by zero' 63 | ``` 64 | 65 | This example illustrates that the `print` expression in `invert` is never evaluated, and instead control is transferred to the suite of the `except` clause in `handler`. Coercing the `ZeroDivisionError` `e` to a string gives the human-interpretable string returned by `handler`: `'division by zero'`. 66 | 67 | ### 3.4.1 Exception Objects 68 | 69 | Exception objects themselves carry attributes, such as the error message stated in an `assert` statement and information about where in the course of execution the exception was raised. User-defined exception classes can carry additional attributes. 70 | 71 | In Chapter 1, we implemented Newton's method to find the zeroes of arbitrary functions. The following example defines an exception class that returns the best guess discovered in the course of iterative improvement whenever a `ValueError` occurs. A math domain error (a type of `ValueError`) is raised when `sqrt` is applied to a negative number. This exception is handled by raising an `IterImproveError` that stores the most recent guess from Newton's method as an attribute. 72 | 73 | First, we define a new class that inherits from `Exception`. 74 | 75 | ``` 76 | >>> class IterImproveError(Exception): 77 | def __init__(self, last_guess): 78 | self.last_guess = last_guess 79 | ``` 80 | 81 | Next, we define a version of `IterImprove`, our generic iterative improvement algorithm. This version handles any `ValueError` by raising an `IterImproveError` that stores the most recent guess. As before, `iter_improve` takes as arguments two functions, each of which takes a single numerical argument. The `update` function returns new guesses, while the `done` function returns a boolean indicating that improvement has converged to a correct value. 82 | 83 | ``` 84 | >>> def iter_improve(update, done, guess=1, max_updates=1000): 85 | k = 0 86 | try: 87 | while not done(guess) and k < max_updates: 88 | guess = update(guess) 89 | k = k + 1 90 | return guess 91 | except ValueError: 92 | raise IterImproveError(guess) 93 | ``` 94 | 95 | Finally, we define `find_root`, which returns the result of `iter_improve` applied to a Newton update function returned by `newton_update`, which is defined in Chapter 1 and requires no changes for this example. This version of `find_root` handles an `IterImproveError` by returning its last guess. 96 | 97 | ``` 98 | >>> def find_root(f, guess=1): 99 | def done(x): 100 | return f(x) == 0 101 | try: 102 | return iter_improve(newton_update(f), done, guess) 103 | except IterImproveError as e: 104 | return e.last_guess 105 | ``` 106 | 107 | Consider applying `find_root` to find the zero of the function (2x^2 + \sqrt{x}). This function has a zero at `0`, but evaluating it on any negative number will raise a `ValueError`. Our Chapter 1 implementation of Newton's Method would raise that error and fail to return any guess of the zero. Our revised implementation returns the last guess found before the error. 108 | 109 | ``` 110 | >>> from math import sqrt 111 | >>> find_root(lambda x: 2*x*x + sqrt(x)) 112 | -0.030211203830201594 113 | ``` 114 | 115 | While this approximation is still far from the correct answer of `0`, some applications would prefer this coarse approximation to a `ValueError`. 116 | 117 | Exceptions are another technique that help us as programs to separate the concerns of our program into modular parts. In this example, Python's exception mechanism allowed us to separate the logic for iterative improvement, which appears unchanged in the suite of the `try` clause, from the logic for handling errors, which appears in `except` clauses. We will also find that exceptions are a very useful feature when implementing interpreters in Python. -------------------------------------------------------------------------------- /03-the-structure-and-interpretation-of-computer-programs/3.5-interpreters-for-languages-with-combination.md: -------------------------------------------------------------------------------- 1 | 2 | ## 3.5 Interpreters for Languages with Combination 3 | 4 | The software running on any modern computer is written in a variety of programming 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 software systems. 5 | 6 | *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 interpreters. An interpreter for a programming language is a function that, when applied to an expression of the language, performs the actions required to evaluate that expression. 7 | 8 | We now embark on a tour of the technology by which languages are established in terms of other languages. We will first define an interpreter for a limited language called Calculator that shares the syntax of Python call expressions. We will then develop a sketch interpreters for the Scheme and Logo languages, which is are dialects of Lisp, the second oldest language still in widespread use today. The interpreter we create will be complete in the sense that it will allow us to write fully general programs in Logo. To do so, it will implement the environment model of evaluation that we have developed over the course of this text. 9 | 10 | ### 3.5.1 Calculator 11 | 12 | Our first new language is Calculator, an expression language for the arithmetic operations of addition, subtraction, multiplication, and division. Calculator shares Python's call expression syntax, but its operators are more flexible in the number of arguments they accept. For instance, the Calculator operators `add` and `mul` take an arbitrary number of arguments: 13 | 14 | ``` 15 | calc> add(1, 2, 3, 4) 16 | 10 17 | calc> mul() 18 | 1 19 | ``` 20 | 21 | The `sub` operator has two behaviors. With one argument, it negates the argument. With at least two arguments, it subtracts all but the first from the first. The `div` operator has the semantics of Python's `operator.truediv` function and takes exactly two arguments: 22 | 23 | ``` 24 | calc> sub(10, 1, 2, 3) 25 | 4 26 | calc> sub(3) 27 | -3 28 | calc> div(15, 12) 29 | 1.25 30 | ``` 31 | 32 | As in Python, call expression nesting provides a means of combination in the Calculator language. To condense notation, the names of operators can also be replaced by their standard symbols: 33 | 34 | ``` 35 | calc> sub(100, mul(7, add(8, div(-12, -3)))) 36 | 16.0 37 | calc> -(100, *(7, +(8, /(-12, -3)))) 38 | 16.0 39 | ``` 40 | 41 | We will implement an interpreter for Calculator in Python. That is, we will write a Python program that takes a string as input and either returns the result of evaluating that string if it is a well-formed Calculator expression or raises an appropriate exception if it is not. The core of the interpreter for the Calculator language is a recursive function called `calc_eval` that evaluates a tree-structured expression object. 42 | 43 | **Expression trees.** Until this point in the course, expression trees have been conceptual entities to which we have referred in describing the process of evaluation; we have never before explicitly represented expression trees as data in our programs. In order to write an interpreter, we must operate on expressions as data. In the course of this chapter, many of the concepts introduced in previous chapters will finally by realized in code. 44 | 45 | A primitive expression is just a number in Calculator, either an `int` or `float` type. All combined expressions are call expressions. A call expression is represented as a class `Exp` that has two attribute instances. The `operator` in Calculator is always a string: an arithmetic operator name or symbol. The `operands` are either primitive expressions or themselves instances of `Exp`. 46 | 47 | ``` 48 | >>> class Exp(object): 49 | """A call expression in Calculator.""" 50 | def __init__(self, operator, operands): 51 | self.operator = operator 52 | self.operands = operands 53 | def __repr__(self): 54 | return 'Exp({0}, {1})'.format(repr(self.operator), repr(self.operands)) 55 | def __str__(self): 56 | operand_strs = ', '.join(map(str, self.operands)) 57 | return '{0}({1})'.format(self.operator, operand_strs) 58 | ``` 59 | 60 | An `Exp` instance defines two string methods. The `__repr__` method returns Python expression, while the `__str__` method returns a Calculator expression. 61 | 62 | ``` 63 | >>> Exp('add', [1, 2]) 64 | Exp('add', [1, 2]) 65 | >>> str(Exp('add', [1, 2])) 66 | 'add(1, 2)' 67 | >>> Exp('add', [1, Exp('mul', [2, 3, 4])]) 68 | Exp('add', [1, Exp('mul', [2, 3, 4])]) 69 | >>> str(Exp('add', [1, Exp('mul', [2, 3, 4])])) 70 | 'add(1, mul(2, 3, 4))' 71 | ``` 72 | 73 | This final example demonstrates how the `Exp` class represents the hierarchical structure in expression trees by including instances of `Exp` as elements of `operands`. 74 | 75 | **Evaluation.** The `calc_eval` function itself takes an expression as an argument and returns its value. It classifies the expression by its form and directs its evaluation. For Calculator, the only two syntactic forms of expressions are numbers and call expressions, which are `Exp` instances. Numbers are *self-evaluating*; they can be returned directly from `calc_eval`. Call expressions require function application. 76 | 77 | ``` 78 | >>> def calc_eval(exp): 79 | """Evaluate a Calculator expression.""" 80 | if type(exp) in (int, float): 81 | return exp 82 | elif type(exp) == Exp: 83 | arguments = list(map(calc_eval, exp.operands)) 84 | return calc_apply(exp.operator, arguments) 85 | ``` 86 | 87 | Call expressions are evaluated by first recursively mapping the `calc_eval` function to the list of operands to compute a list of `arguments`. Then, the operator is applied to those arguments in a second function, `calc_apply`. 88 | 89 | The Calculator language is simple enough that we can easily express the logic of applying each operator in the body of a single function. In `calc_apply`, each conditional clause corresponds to applying one operator. 90 | 91 | ``` 92 | >>> from operator import mul 93 | >>> from functools import reduce 94 | >>> def calc_apply(operator, args): 95 | """Apply the named operator to a list of args.""" 96 | if operator in ('add', '+'): 97 | return sum(args) 98 | if operator in ('sub', '-'): 99 | if len(args) == 0: 100 | raise TypeError(operator + ' requires at least 1 argument') 101 | if len(args) == 1: 102 | return -args[0] 103 | return sum(args[:1] + [-arg for arg in args[1:]]) 104 | if operator in ('mul', '*'): 105 | return reduce(mul, args, 1) 106 | if operator in ('div', '/'): 107 | if len(args) != 2: 108 | raise TypeError(operator + ' requires exactly 2 arguments') 109 | numer, denom = args 110 | return numer/denom 111 | ``` 112 | 113 | Above, each suite computes the result of a different operator, or raises an appropriate `TypeError` when the wrong number of arguments is given. The `calc_apply` function can be applied directly, but it must be passed a list of *values* as arguments rather than a list of operand expressions. 114 | 115 | ``` 116 | >>> calc_apply('+', [1, 2, 3]) 117 | 6 118 | >>> calc_apply('-', [10, 1, 2, 3]) 119 | 4 120 | >>> calc_apply('*', []) 121 | 1 122 | >>> calc_apply('/', [40, 5]) 123 | 8.0 124 | ``` 125 | 126 | The role of `calc_eval` is to make proper calls to `calc_apply` by first computing the value of operand sub-expressions before passing them as arguments to `calc_apply`. Thus, `calc_eval` can accept a nested expression. 127 | 128 | ``` 129 | >>> e = Exp('add', [2, Exp('mul', [4, 6])]) 130 | >>> str(e) 131 | 'add(2, mul(4, 6))' 132 | >>> calc_eval(e) 133 | 26 134 | ``` 135 | 136 | The structure of `calc_eval` is an example of dispatching on type: the form of the expression. The first form of expression is a number, which requires no additional evaluation step. In general, primitive expressions that do not require an additional evaluation step are called *self-evaluating*. The only self-evaluating expressions in our Calculator language are numbers, but a general programming language might also include strings, boolean values, etc. 137 | 138 | **Read-eval-print loops.** A typical approach to interacting with an interpreter is through a read-eval-print loop, or REPL, which is a mode of interaction that reads an expression, evaluates it, and prints the result for the user. The Python interactive session is an example of such a loop. 139 | 140 | An implementation of a REPL can be largely independent of the interpreter it uses. The function `read_eval_print_loop` below takes as input a line of text from the user with the built-in `input` function. It constructs an expression tree using the language-specific `calc_parse` function, defined in the following section on parsing. Finally, it prints the result of applying `calc_eval` to the expression tree returned by `calc_parse`. 141 | 142 | ``` 143 | >>> def read_eval_print_loop(): 144 | """Run a read-eval-print loop for calculator.""" 145 | while True: 146 | expression_tree = calc_parse(input('calc> ')) 147 | print(calc_eval(expression_tree)) 148 | ``` 149 | 150 | This version of `read_eval_print_loop` contains all of the essential components of an interactive interface. An example session would look like: 151 | 152 | ``` 153 | calc> mul(1, 2, 3) 154 | 6 155 | calc> add() 156 | 0 157 | calc> add(2, div(4, 8)) 158 | 2.5 159 | ``` 160 | 161 | This loop implementation has no mechanism for termination or error handling. We can improve the interface by reporting errors to the user. We can also allow the user to exit the loop by signalling a keyboard interrupt (`Control-C` on UNIX) or end-of-file exception (`Control-D` on UNIX). To enable these improvements, we place the original suite of the `while` statement within a `try` statement. The first `except` clause handles `SyntaxError` exceptions raised by `calc_parse` as well as `TypeError` and `ZeroDivisionError` exceptions raised by `calc_eval`. 162 | 163 | ``` 164 | >>> def read_eval_print_loop(): 165 | """Run a read-eval-print loop for calculator.""" 166 | while True: 167 | try: 168 | expression_tree = calc_parse(input('calc> ')) 169 | print(calc_eval(expression_tree)) 170 | except (SyntaxError, TypeError, ZeroDivisionError) as err: 171 | print(type(err).__name__ + ':', err) 172 | except (KeyboardInterrupt, EOFError): # -D, etc. 173 | print('Calculation completed.') 174 | return 175 | ``` 176 | 177 | This loop implementation reports errors without exiting the loop. Rather than exiting the program on an error, restarting the loop after an error message lets users revise their expressions. Upon importing the `readline` module, users can even recall their previous inputs using the up arrow or `Control-P`. The final result provides an informative error reporting interface: 178 | 179 | ``` 180 | calc> add 181 | SyntaxError: expected ( after add 182 | calc> div(5) 183 | TypeError: div requires exactly 2 arguments 184 | calc> div(1, 0) 185 | ZeroDivisionError: division by zero 186 | calc> ^DCalculation completed. 187 | ``` 188 | 189 | As we generalize our interpreter to new languages other than Calculator, we will see that the `read_eval_print_loop` is parameterized by a parse function, an evaluation function, and the exception types handled by the `try` statement. Beyond these changes, all REPLs can be implemented using the same structure. 190 | 191 | ### 3.5.2 Parsing 192 | 193 | Parsing is the process of generating expression trees from raw text input. It is the job of the evaluation function to interpret those expression trees, but the parser must supply well-formed expression trees to the evaluator. A parser is in fact a composition of two components: a lexical analyzer and a syntactic analyzer. First, the lexical analyzer partitions the input string into *tokens*, which are the minimal syntactic units of the language, such as names and symbols. Second, the syntactic analyzer constructs an expression tree from this sequence of tokens. 194 | 195 | ``` 196 | >>> def calc_parse(line): 197 | """Parse a line of calculator input and return an expression tree.""" 198 | tokens = tokenize(line) 199 | expression_tree = analyze(tokens) 200 | if len(tokens) > 0: 201 | raise SyntaxError('Extra token(s): ' + ' '.join(tokens)) 202 | return expression_tree 203 | ``` 204 | 205 | The sequence of tokens produced by the lexical analyzer, called `tokenize`, is consumed by the syntactic analyzer, called `analyze`. In this case, we define `calc_parse` to expect only one well-formed Calculator expression. Parsers for some languages are designed to accept multiple expressions delimited by new line characters, semicolons, or even spaces. We defer this additional complexity until we introduce the Logo language below. 206 | 207 | **Lexical analysis.** The component that interprets a string as a token sequence is called a *tokenizer* or *lexical analyzer*. In our implementation, the tokenizer is a function called `tokenize`. The Calculator language consists of symbols that include numbers, operator names, and operator symbols, such as `+`. These symbols are always separated by two types of delimiters: commas and parentheses. Each symbol is its own token, as is each comma and parenthesis. Tokens can be separated by adding spaces to the input string and then splitting the string at each space. 208 | 209 | ``` 210 | >>> def tokenize(line): 211 | """Convert a string into a list of tokens.""" 212 | spaced = line.replace('(',' ( ').replace(')',' ) ').replace(',', ' , ') 213 | return spaced.split() 214 | ``` 215 | 216 | Tokenizing a well-formed Calculator expression keeps names intact, but separates all symbols and delimiters. 217 | 218 | ``` 219 | >>> tokenize('add(2, mul(4, 6))') 220 | ['add', '(', '2', ',', 'mul', '(', '4', ',', '6', ')', ')'] 221 | ``` 222 | 223 | Languages with a more complicated syntax may require a more sophisticated tokenizer. In particular, many tokenizers resolve the syntactic type of each token returned. For example, the type of a token in Calculator may be an operator, a name, a number, or a delimiter. This classification can simplify the process of *parsing* the token sequence. 224 | 225 | **Syntactic analysis.** The component that interprets a token sequence as an expression tree is called a *syntactic analyzer*. In our implementation, syntactic analysis is performed by a recursive function called `analyze`. It is recursive because analyzing a sequence of tokens often involves analyzing a subsequence of those tokens into an expression tree, which itself serves as a branch (i.e., operand) of a larger expression tree. Recursion generates the hierarchical structures consumed by the evaluator. 226 | 227 | The `analyze` function expects a list of tokens that begins with a well-formed expression. It analyzes the first token, coercing strings that represent numbers into numeric values. It then must consider the two legal expression types in the Calculator language. Numeric tokens are themselves complete, primitive expression trees. Combined expressions begin with an operator and follow with a list of operand expressions delimited by parentheses. Operands are analyzed by the `analyze_operands` function, which recursively calls `analyze` on each operand expression. We begin with an implementation that does not check for syntax errors. 228 | 229 | ``` 230 | >>> def analyze(tokens): 231 | """Create a tree of nested lists from a sequence of tokens.""" 232 | token = analyze_token(tokens.pop(0)) 233 | if type(token) in (int, float): 234 | return token 235 | else: 236 | tokens.pop(0) # Remove ( 237 | return Exp(token, analyze_operands(tokens)) 238 | >>> def analyze_operands(tokens): 239 | """Read a list of comma-separated operands.""" 240 | operands = [] 241 | while tokens[0] != ')': 242 | if operands: 243 | tokens.pop(0) # Remove , 244 | operands.append(analyze(tokens)) 245 | tokens.pop(0) # Remove ) 246 | return operands 247 | ``` 248 | 249 | Finally, we need to implement `analyze_token`. The `analyze_token` function that converts number literals into numbers. Rather than implementing this logic ourselves, we rely on built-in Python type coercion, using the `int` and `float` constructors to convert tokens to those types. 250 | 251 | ``` 252 | >>> def analyze_token(token): 253 | """Return the value of token if it can be analyzed as a number, or token.""" 254 | try: 255 | return int(token) 256 | except (TypeError, ValueError): 257 | try: 258 | return float(token) 259 | except (TypeError, ValueError): 260 | return token 261 | ``` 262 | 263 | Our implementation of `analyze` is complete; it correctly parses well-formed Calculator expressions into expression trees. These trees can be converted back into Calculator expressions by the `str` function. 264 | 265 | ``` 266 | >>> expression = 'add(2, mul(4, 6))' 267 | >>> analyze(tokenize(expression)) 268 | Exp('add', [2, Exp('mul', [4, 6])]) 269 | >>> str(analyze(tokenize(expression))) 270 | 'add(2, mul(4, 6))' 271 | ``` 272 | 273 | The `analyze` function is meant to return only well-formed expression trees, and so it must detect errors in the syntax of its input. In particular, it must detect that expressions are complete, correctly delimited, and use only known operators. The following revisions ensure that each step of the syntactic analysis finds the token it expects. 274 | 275 | ``` 276 | >>> known_operators = ['add', 'sub', 'mul', 'div', '+', '-', '*', '/'] 277 | >>> def analyze(tokens): 278 | """Create a tree of nested lists from a sequence of tokens.""" 279 | assert_non_empty(tokens) 280 | token = analyze_token(tokens.pop(0)) 281 | if type(token) in (int, float): 282 | return token 283 | if token in known_operators: 284 | if len(tokens) == 0 or tokens.pop(0) != '(': 285 | raise SyntaxError('expected ( after ' + token) 286 | return Exp(token, analyze_operands(tokens)) 287 | else: 288 | raise SyntaxError('unexpected ' + token) 289 | >>> def analyze_operands(tokens): 290 | """Analyze a sequence of comma-separated operands.""" 291 | assert_non_empty(tokens) 292 | operands = [] 293 | while tokens[0] != ')': 294 | if operands and tokens.pop(0) != ',': 295 | raise SyntaxError('expected ,') 296 | operands.append(analyze(tokens)) 297 | assert_non_empty(tokens) 298 | tokens.pop(0) # Remove ) 299 | return elements 300 | >>> def assert_non_empty(tokens): 301 | """Raise an exception if tokens is empty.""" 302 | if len(tokens) == 0: 303 | raise SyntaxError('unexpected end of line') 304 | ``` 305 | 306 | Informative syntax errors improve the usability of an interpreter substantially. Above, the `SyntaxError` exceptions that are raised include a description of the problem encountered. These error strings also serve to document the definitions of these analysis functions. 307 | 308 | This definition completes our Calculator interpreter. A single Python 3 source file [calc.py](https://wizardforcel.gitbooks.io/sicp-in-python/content/calc.py) is available for your experimentation. Our interpreter is robust to errors, in the sense that every input that a user enters at the `calc>` prompt will either be evaluated to a number or raise an appropriate error that describes why the input is not a well-formed Calculator expression. -------------------------------------------------------------------------------- /04-distributed-and-parallel-computing/4.1-introduction.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 4: Distributed and Parallel Computing 3 | 4 | ## 4.1 Introduction 5 | 6 | So far, we have focused on how to create, interpret, and execute programs. In Chapter 1, we learned to use functions as a means for combination and abstraction. Chapter 2 showed us how to represent data and manipulate it with data structures and objects, and introduced us to the concept of data abstraction. In Chapter 3, we learned how computer programs are interpreted and executed. The result is that we understand how to design programs for a single processor to run. 7 | 8 | In this chapter, we turn to the problem of coordinating multiple computers and processors. First, we will look at distributed systems. These are interconnected groups of independent computers that need to communicate with each other to get a job done. They may need to coordinate to provide a service, share data, or even store data sets that are too large to fit on a single machine. We will look at different roles computers can play in distributed systems and learn about the kinds of information that computers need to exchange in order to work together. 9 | 10 | Next, we will consider concurrent computation, also known as parallel computation. Concurrent computation is when a single program is executed by multiple processors with a shared memory, all working together in parallel in order to get work done faster. Concurrency introduces new challenges, and so we will develop new techniques to manage the complexity of concurrent programs. -------------------------------------------------------------------------------- /04-distributed-and-parallel-computing/4.2-distributed-computing.md: -------------------------------------------------------------------------------- 1 | 2 | ## 4.2 Distributed Computing 3 | 4 | A distributed system is a network of autonomous computers that communicate with each other in order to achieve a goal. The computers in a distributed system are independent and do not physically share memory or processors. They communicate with each other using *messages*, pieces of information transferred from one computer to another over a network. Messages can communicate many things: computers can tell other computers to execute a procedures with particular arguments, they can send and receive packets of data, or send signals that tell other computers to behave a certain way. 5 | 6 | Computers in a distributed system can have different roles. A computer's role depends on the goal of the system and the computer's own hardware and software properties. There are two predominant ways of organizing computers in a distributed system. The first is the client-server architecture, and the second is the peer-to-peer architecture. 7 | 8 | ### 4.2.1 Client/Server Systems 9 | 10 | The client-server architecture is a way to dispense a service from a central source. There is a single *server* that provides a service, and multiple *clients* that communicate with the server to consume its products. In this architecture, clients and servers have different jobs. The server's job is to respond to service requests from clients, while a client's job is to use the data provided in response in order to perform some task. 11 | 12 | ![](img/04/clientserver.png) 13 | 14 | The client-server model of communication can be traced back to the introduction of UNIX in the 1970's, but perhaps the most influential use of the model is the modern World Wide Web. An example of a client-server interaction is reading the New York Times online. When the web server at www.nytimes.com is contacted by a web browsing client (like Firefox), its job is to send back the HTML of the New York Times main page. This could involve calculating personalized content based on user account information sent by the client, and fetching appropriate advertisements. The job of the web browsing client is to render the HTML code sent by the server. This means displaying the images, arranging the content visually, showing different colors, fonts, and shapes and allowing users to interact with the rendered web page. 15 | 16 | The concepts of *client* and *server* are powerful functional abstractions. A server is simply a unit that provides a service, possibly to multiple clients simultaneously, and a client is a unit that consumes the service. The clients do not need to know the details of how the service is provided, or how the data they are receiving is stored or calculated, and the server does not need to know how the data is going to be used. 17 | 18 | On the web, we think of clients and servers as being on different machines, but even systems on a single machine can have client/server architectures. For example, signals from input devices on a computer need to be generally available to programs running on the computer. The programs are clients, consuming mouse and keyboard input data. The operating system's device drivers are the servers, taking in physical signals and serving them up as usable input. 19 | 20 | A drawback of client-server systems is that the server is a single point of failure. It is the only component with the ability to dispense the service. There can be any number of clients, which are interchangeable and can come and go as necessary. If the server goes down, however, the system stops working. Thus, the functional abstraction created by the client-server architecture also makes it vulnerable to failure. 21 | 22 | Another drawback of client-server systems is that resources become scarce if there are too many clients. Clients increase the demand on the system without contributing any computing resources. Client-server systems cannot shrink and grow with changing demand. 23 | 24 | ### 4.2.2 Peer-to-peer Systems 25 | 26 | The client-server model is appropriate for service-oriented situations. However, there are other computational goals for which a more equal division of labor is a better choice. The term *peer-to-peer* is used to describe distributed systems in which labor is divided among all the components of the system. All the computers send and receive data, and they all contribute some processing power and memory. As a distributed system increases in size, its capacity of computational resources increases. In a peer-to-peer system, all components of the system contribute some processing power and memory to a distributed computation. 27 | 28 | Division of labor among *all* participants is the identifying characteristic of a peer-to-peer system. This means that peers need to be able to communicate with each other reliably. In order to make sure that messages reach their intended destinations, peer-to-peer systems need to have an organized network structure. The components in these systems cooperate to maintain enough information about the locations of other components to send messages to intended destinations. 29 | 30 | In some peer-to-peer systems, the job of maintaining the health of the network is taken on by a set of specialized components. Such systems are not pure peer-to-peer systems, because they have different types of components that serve different functions. The components that support a peer-to-peer network act like scaffolding: they help the network stay connected, they maintain information about the locations of different computers, and they help newcomers take their place within their neighborhood. 31 | 32 | The most common applications of peer-to-peer systems are data transfer and data storage. For data transfer, each computer in the system contributes to send data over the network. If the destination computer is in a particular computer's neighborhood, that computer helps send data along. For data storage, the data set may be too large to fit on any single computer, or too valuable to store on just a single computer. Each computer stores a small portion of the data, and there may be multiple copies of the same data spread over different computers. When a computer fails, the data that was on it can be restored from other copies and put back when a replacement arrives. 33 | 34 | Skype, the voice- and video-chat service, is an example of a data transfer application with a peer-to-peer architecture. When two people on different computers are having a Skype conversation, their communications are broken up into packets of 1s and 0s and transmitted through a peer-to-peer network. This network is composed of other people whose computers are signed into Skype. Each computer knows the location of a few other computers in its neighborhood. A computer helps send a packet to its destination by passing it on a neighbor, which passes it on to some other neighbor, and so on, until the packet reaches its intended destination. Skype is not a pure peer-to-peer system. A scaffolding network of *supernodes* is responsible for logging-in and logging-out users, maintaining information about the locations of their computers, and modifying the network structure to deal with users entering and leaving. 35 | 36 | ### 4.2.3 Modularity 37 | 38 | The two architectures we have just considered -- peer-to-peer and client-server -- are designed to enforce *modularity*. Modularity is the idea that the components of a system should be black boxes with respect to each other. It should not matter how a component implements its behavior, as long as it upholds an *interface*: a specification for what outputs will result from inputs. 39 | 40 | In chapter 2, we encountered interfaces in the context of dispatch functions and object-oriented programming. There, interfaces took the form of specifying the messages that objects should take, and how they should behave in response to them. For example, in order to uphold the "representable as strings" interface, an object must be able to respond to the `__repr__` and `__str__` messages, and output appropriate strings in response. How the generation of those strings is implemented is not part of the interface. 41 | 42 | In distributed systems, we must consider program design that involves multiple computers, and so we extend this notion of an interface from objects and messages to full programs. An interface specifies the inputs that should be accepted and the outputs that should be returned in response to inputs. Interfaces are everywhere in the real world, and we often take them for granted. A familiar example is TV remotes. You can buy many different brands of remote for a modern TV, and they will all work. The only commonality between them is the "TV remote" interface. A piece of electronics obeys the "TV remote" interface as long as it sends the correct signals to your TV (the output) in response to when you press the power, volume, channel, or whatever other buttons (the input). 43 | 44 | Modularity gives a system many advantages, and is a property of thoughtful system design. First, a modular system is easy to understand. This makes it easier to change and expand. Second, if something goes wrong with the system, only the defective components need to be replaced. Third, bugs or malfunctions are easy to localize. If the output of a component doesn't match the specifications of its interface, even though the inputs are correct, then that component is the source of the malfunction. 45 | 46 | ### 4.2.4 Message Passing 47 | 48 | In distributed systems, components communicate with each other using message passing. A message has three essential parts: the sender, the recipient, and the content. The sender needs to be specified so that the recipient knows which component sent the message, and where to send replies. The recipient needs to be specified so that any computers who are helping send the message know where to direct it. The content of the message is the most variable. Depending on the function of the overall system, the content can be a piece of data, a signal, or instructions for the remote computer to evaluate a function with some arguments. 49 | 50 | This notion of message passing is closely related to the message passing technique from Chapter 2, in which dispatch functions or dictionaries responded to string-valued messages. Within a program, the sender and receiver are identified by the rules of evaluation. In a distributed system however, the sender and receiver must be explicitly encoded in the message. Within a program, it is convenient to use strings to control the behavior of the dispatch function. In a distributed system, messages may need to be sent over a network, and may need to hold many different kinds of signals as 'data', so they are not always encoded as strings. In both cases, however, messages serve the same function. Different components (dispatch functions or computers) exchange them in order to achieve a goal that requires coordinating multiple modular components. 51 | 52 | At a high level, message contents can be complex data structures, but at a low level, messages are simply streams of 1s and 0s sent over a network. In order to be usable, all messages sent over a network must be formatted according to a consistent *message protocol*. 53 | 54 | A **message protocol** is a set of rules for encoding and decoding messages. Many message protocols specify that a message conform to a particular format, in which certain bits have a consistent meaning. A fixed format implies fixed encoding and decoding rules to generate and read that format. All the components in the distributed system must understand the protocol in order to communicate with each other. That way, they know which part of the message corresponds to which information. 55 | 56 | Message protocols are not particular programs or software libraries. Instead, they are rules that can be applied by a variety of programs, even written in different programming languages. As a result, computers with vastly different software systems can participate in the same distributed system, simply by conforming to the message protocols that govern the system. 57 | 58 | ### 4.2.5 Messages on the World Wide Web 59 | 60 | **HTTP** (short for Hypertext Transfer Protocol) is the message protocol that supports the world wide web. It specifies the format of messages exchanged between a web browser and a web server. All web browsers use the HTTP format to request pages from a web server, and all web servers use the HTTP format to send back their responses. 61 | 62 | When you type in a URL into your web browser, say http://en.wikipedia.org/wiki/UC_Berkeley , you are in fact telling your browser that it must request the page "wiki/UC_Berkeley" from the server called "en.wikipedia.org" using the "http" protocol. The sender of the message is your computer, the recipient is en.wikipedia.org, and the format of the message content is: 63 | 64 | ``` 65 | GET /wiki/UC_Berkeley HTTP/1.1 66 | ``` 67 | 68 | The first word is the type of the request, the next word is the resource that is requested, and after that is the name of the protocol (HTTP) and the version (1.1). (There are another types of requests, such as PUT, POST, and HEAD, that web browsers can also use). 69 | 70 | The server sends back a reply. This time, the sender is en.wikipedia.org, the recipient is your computer, and the format of the message content is a header, followed by data: 71 | 72 | ``` 73 | HTTP/1.1 200 OK 74 | Date: Mon, 23 May 2011 22:38:34 GMT 75 | Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux) 76 | Last-Modified: Wed, 08 Jan 2011 23:11:55 GMT 77 | Content-Type: text/html; charset=UTF-8 78 | 79 | ... web page content ... 80 | ``` 81 | 82 | On the first line, the words "200 OK" mean that there were no errors. The subsequent lines of the header give information about the server, the date, and the type of content being sent back. The header is separated from the actual content of the web page by a blank line. 83 | 84 | If you have typed in a wrong web address, or clicked on a broken link, you may have seen a message like this error: 85 | 86 | ``` 87 | 404 Error File Not Found 88 | ``` 89 | 90 | It means that the server sent back an HTTP header that started like this: 91 | 92 | ``` 93 | HTTP/1.1 404 Not Found 94 | ``` 95 | 96 | A fixed set of response codes is a common feature of a message protocol. Designers of protocols attempt to anticipate common messages that will be sent via the protocol and assign fixed codes to reduce transmission size and establish a common message semantics. In the HTTP protocol, the 200 response code indicates success, while 404 indicates an error that a resource was not found. A variety of other [response codes](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) exist in the HTTP 1.1 standard as well. 97 | 98 | HTTP is a fixed format for communication, but it allows arbitrary web pages to be transmitted. Other protocols like this on the internet are XMPP, a popular protocol for instant messages, and FTP, a protocol for downloading and uploading files between client and server. -------------------------------------------------------------------------------- /05-sequences-and-coroutines/5.1-introduction.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 5: Sequences and Coroutines 3 | 4 | ## 5.1 Introduction 5 | 6 | In this chapter, we continue our discussion of real-world applications by developing new tools to process sequential data. In Chapter 2, we introduced a sequence interface, implemented in Python by built-in data types such as `tuple` and `list`. Sequences supported two operations: querying their length and accessing an element by index. In Chapter 3, we developed a user-defined implementations of the sequence interface, the `Rlist` class for representing recursive lists. These sequence types proved effective for representing and accessing a wide variety of sequential datasets. 7 | 8 | However, representing sequential data using the sequence abstraction has two important limitations. The first is that a sequence of length *n* typically takes up an amount of memory proportional to *n*. Therefore, the longer a sequence is, the more memory it takes to represent it. 9 | 10 | The second limitation of sequences is that sequences can only represent datasets of known, finite length. Many sequential collections that we may want to represent do not have a well-defined length, and some are even infinite. Two mathematical examples of infinite sequences are the positive integers and the Fibonacci numbers. Sequential data sets of unbounded length also appear in other computational domains. For instance, the sequence of all Twitter posts grows longer with every second and therefore does not have a fixed length. Likewise, the sequence of telephone calls sent through a cell tower, the sequence of mouse movements made by a computer user, and the sequence of acceleration measurements from sensors on an aircraft all extend without bound as the world evolves. 11 | 12 | In this chapter, we introduce new constructs for working with sequential data that are designed to accommodate collections of unknown or unbounded length, while using limited memory. We also discuss how these tools can be used with a programming construct called a coroutine to create efficient, modular data processing pipelines. -------------------------------------------------------------------------------- /05-sequences-and-coroutines/5.3-coroutines.md: -------------------------------------------------------------------------------- 1 | 2 | ## 5.3 Coroutines 3 | 4 | Much of this text has focused on techniques for decomposing complex programs into small, modular components. When the logic for a function with complex behavior is divided into several self-contained steps that are themselves functions, these functions are called helper functions or *subroutines*. Subroutines are called by a main function that is responsible for coordinating the use of several subroutines. 5 | 6 | ![](img/05/subroutine.png) 7 | 8 | In this section, we introduce a different way of decomposing complex computations using *coroutines*, an approach that is particularly applicable to the task of processing sequential data. Like a subroutine, a coroutine computes a single step of a complex computation. However, when using coroutines, there is no main function to coordinate results. Instead coroutines themselves link together to form a pipeline. There may be a coroutine for consuming the incoming data and sending it to other coroutines. There may be coroutines that each do simple processing steps on data sent to them, and there may finally be another coroutine that outputs a final result. 9 | 10 | ![](img/05/coroutine.png) 11 | 12 | The difference between coroutines and subroutines is conceptual: subroutines slot into an overarching function to which they are subordinate, whereas coroutines are all colleagues, they cooperate to form a pipeline without any supervising function responsible for calling them in a particular order. 13 | 14 | In this section, we will learn how Python supports building coroutines with the `yield` and `send()` statements. Then, we will look at different roles that coroutines can play in a pipeline, and how coroutines can support multitasking. 15 | 16 | ### 5.3.1 Python Coroutines 17 | 18 | In the previous section, we introduced generator functions, which use `yield` to return values. Python generator functions can also consume values using a `(yield)` statement. In addition two new methods on generator objects, `send()` and `close()`, create a framework for objects that *consume* and produce values. Generator functions that define these objects are coroutines. 19 | 20 | Coroutines consume values using a `(yield)` statement as follows: 21 | 22 | ``` 23 | value = (yield) 24 | ``` 25 | 26 | With this syntax, execution pauses at this statement until the object's `send` method is invoked with an argument: 27 | 28 | ``` 29 | coroutine.send(data) 30 | ``` 31 | 32 | Then, execution resumes, with `value` being assigned to the value of `data`. To signal the end of a computation, we shut down a coroutine using the `close()` method. This raises a `GeneratorExit` exception inside the coroutine, which we can catch with a `try/except` clause. 33 | 34 | The example below illustrates these concepts. It is a coroutine that prints strings that match a provided pattern. 35 | 36 | ``` 37 | >>> def match(pattern): 38 | print('Looking for ' + pattern) 39 | try: 40 | while True: 41 | s = (yield) 42 | if pattern in s: 43 | print(s) 44 | except GeneratorExit: 45 | print("=== Done ===") 46 | ``` 47 | 48 | We initialize it with a pattern, and call `__next__()` to start execution: 49 | 50 | ``` 51 | >>> m = match("Jabberwock") 52 | >>> m.__next__() 53 | Looking for Jabberwock 54 | ``` 55 | 56 | The call to `__next__()` causes the body of the function to be executed, so the line "Looking for jabberwock" gets printed out. Execution continues until the statement `line = (yield)` is encountered. Then, execution pauses, and waits for a value to be sent to `m`. We can send values to it using `send`. 57 | 58 | ``` 59 | >>> m.send("the Jabberwock with eyes of flame") 60 | the Jabberwock with eyes of flame 61 | >>> m.send("came whiffling through the tulgey wood") 62 | >>> m.send("and burbled as it came") 63 | >>> m.close() 64 | === Done === 65 | ``` 66 | 67 | When we call `m.send` with a value, evaluation resumes inside the coroutine `m` at the statement `line = (yield)`, where the sent value is assigned to the variable `line`. Evaluation continues inside `m`, printing out the line if it matches, going through the loop until it encounters `line = (yield)` again. Then, evaluation pauses inside `m` and resumes where `m.send` was called. 68 | 69 | We can chain functions that `send()` and functions that `yield` together achieve complex behaviors. For example, the function below splits a string named `text` into words and sends each word to another coroutine. 70 | 71 | ``` 72 | >>> def read(text, next_coroutine): 73 | for line in text.split(): 74 | next_coroutine.send(line) 75 | next_coroutine.close() 76 | ``` 77 | 78 | Each word is sent to the coroutine bound to `next_coroutine`, causing `next_coroutine` to start executing, and this function to pause and wait. It waits until `next_coroutine` pauses, at which point the function resumes by sending the next word or completing. 79 | 80 | If we chain this function together with `match` defined above, we can create a program that prints out only the words that match a particular word. 81 | 82 | ``` 83 | >>> text = 'Commending spending is offending to people pending lending!' 84 | >>> matcher = match('ending') 85 | >>> matcher.__next__() 86 | Looking for ending 87 | >>> read(text, matcher) 88 | Commending 89 | spending 90 | offending 91 | pending 92 | lending! 93 | === Done === 94 | ``` 95 | 96 | The `read` function sends each word to the coroutine `matcher`, which prints out any input that matches its `pattern`. Within the `matcher` coroutine, the line `s = (yield)` waits for each sent word, and it transfers control back to `read` when it is reached. 97 | 98 | ![](img/05/read-match-coroutine.png) 99 | 100 | ### 5.3.2 Produce, Filter, and Consume 101 | 102 | Coroutines can have different roles depending on how they use `yield` and `send()`: 103 | 104 | ![](img/05/produce-filter-consume.png) 105 | 106 | - A **Producer** creates items in a series and uses send(), but not `(yield)` 107 | - A **Filter** uses `(yield)` to consume items and `send()` to send result to a next step. 108 | - A **Consumer** uses `(yield)` to consume items, but does not send. 109 | 110 | The function `read` above is an example of a *producer*. It does not use `(yield)`, but uses `send` to produce data items. The function `match` is an example of a consumer. It does not `send` anything, but consumes data with `(yield)`.We can break up `match` into a filter and a consumer. The filter would be a coroutine that only sends on strings that match its pattern. 111 | 112 | ``` 113 | >>> def match_filter(pattern, next_coroutine): 114 | print('Looking for ' + pattern) 115 | try: 116 | while True: 117 | s = (yield) 118 | if pattern in s: 119 | next_coroutine.send(s) 120 | except GeneratorExit: 121 | next_coroutine.close() 122 | ``` 123 | 124 | And the consumer would be a function that printed out lines sent to it. 125 | 126 | ``` 127 | >>> def print_consumer(): 128 | print('Preparing to print') 129 | try: 130 | while True: 131 | line = (yield) 132 | print(line) 133 | except GeneratorExit: 134 | print("=== Done ===") 135 | ``` 136 | 137 | When a filter or consumer is constructed, its `__next__` method must be invoked to start its execution. 138 | 139 | ``` 140 | >>> printer = print_consumer() 141 | >>> printer.__next__() 142 | Preparing to print 143 | >>> matcher = match_filter('pend', printer) 144 | >>> matcher.__next__() 145 | Looking for pend 146 | >>> read(text, matcher) 147 | spending 148 | pending 149 | === Done === 150 | ``` 151 | 152 | Even though the name *filter* implies removing items, filters can transform items as well. The function below is an example of a filter that transforms items. It consumes strings and sends along a dictionary of the number of times different letters occur in the string. 153 | 154 | ``` 155 | >>> def count_letters(next_coroutine): 156 | try: 157 | while True: 158 | s = (yield) 159 | counts = {letter:s.count(letter) for letter in set(s)} 160 | next_coroutine.send(counts) 161 | except GeneratorExit as e: 162 | next_coroutine.close() 163 | ``` 164 | 165 | We can use it to count the most frequently-used letters in text using a consumer that adds up dictionaries and finds the most frequent key. 166 | 167 | ``` 168 | >>> def sum_dictionaries(): 169 | total = {} 170 | try: 171 | while True: 172 | counts = (yield) 173 | for letter, count in counts.items(): 174 | total[letter] = count + total.get(letter, 0) 175 | except GeneratorExit: 176 | max_letter = max(total.items(), key=lambda t: t[1])[0] 177 | print("Most frequent letter: " + max_letter) 178 | ``` 179 | 180 | To run this pipeline on a file, we must first read the lines of a file one-by-one. Then, we send the results through `count_letters` and finally to `sum_dictionaries`. We can re-use the `read` coroutine to read the lines of a file. 181 | 182 | ``` 183 | >>> s = sum_dictionaries() 184 | >>> s.__next__() 185 | >>> c = count_letters(s) 186 | >>> c.__next__() 187 | >>> read(text, c) 188 | Most frequent letter: n 189 | ``` 190 | 191 | ### 5.3.3 Multitasking 192 | 193 | A producer or filter does not have to be restricted to just one next step. It can have multiple coroutines downstream of it, and `send()` data to all of them. For example, here is a version of `read` that sends the words in a string to multiple next steps. 194 | 195 | ``` 196 | >>> def read_to_many(text, coroutines): 197 | for word in text.split(): 198 | for coroutine in coroutines: 199 | coroutine.send(word) 200 | for coroutine in coroutines: 201 | coroutine.close() 202 | ``` 203 | 204 | We can use it to examine the same text for multiple words: 205 | 206 | ``` 207 | >>> m = match("mend") 208 | >>> m.__next__() 209 | Looking for mend 210 | >>> p = match("pe") 211 | >>> p.__next__() 212 | Looking for pe 213 | >>> read_to_many(text, [m, p]) 214 | Commending 215 | spending 216 | people 217 | pending 218 | === Done === 219 | === Done === 220 | ``` 221 | 222 | First, `read_to_many` calls `send(word)` on `m`. The coroutine, which is waiting at `text = (yield)` runs through its loop, prints out a match if found, and resumes waiting for the next `send`. Execution then returns to `read_to_many`, which proceeds to send the same line to `p`. Thus, the words of `text` are printed in order. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SICP Python epub compatible 2 | 3 | I love the rewriting of [SICP with the Python language](https://www-inst.eecs.berkeley.edu//~cs61a/sp12/book/) with distributed and parallel computing... But we just found it in HTML so I've decided to convert it in any format using `pandoc` 4 | 5 | ## Steps 6 | 7 | If you don't want to build by yourself you can just download the ebook.epub. Otherwise you have to: 8 | 9 | 1. Install pandoc, on macOS just use `brew install pandoc` 10 | 2. Buil all chapters 11 | 12 | ``` 13 | pandoc introduction.md 01-building-abstractions-with-functions/1.1-introduction.md 01-building-abstractions-with-functions/1.2-the-elements-of-programming.md 01-building-abstractions-with-functions/1.3-define-new-functions.md 01-building-abstractions-with-functions/1.4-pratical-guidance-the-art-of-the-function.md 01-building-abstractions-with-functions/1.5-control.md 01-building-abstractions-with-functions/1.6-high-order-functions.md 02-buiding-abstractions-with-objects/2.1-introduction.md 02-buiding-abstractions-with-objects/2.2-data-abstraction.md 02-buiding-abstractions-with-objects/2.3-sequences.md 02-buiding-abstractions-with-objects/2.4-mutable-data.md 02-buiding-abstractions-with-objects/2.5-object-oriented-programming.md 02-buiding-abstractions-with-objects/2.6-implementing-classes-and-objects.md 02-buiding-abstractions-with-objects/2.7-generic-operations.md 03-the-structure-and-interpretation-of-computer-programs/3.1-introduction.md 03-the-structure-and-interpretation-of-computer-programs/3.2-functions-and-the-processes-they-generate.md 03-the-structure-and-interpretation-of-computer-programs/3.3-recursive-data-structures.md 03-the-structure-and-interpretation-of-computer-programs/3.4-exceptions.md 03-the-structure-and-interpretation-of-computer-programs/3.5-interpreters-for-languages-with-combination.md 03-the-structure-and-interpretation-of-computer-programs/3.6-interpreters-for-languages-with-abstraction.md 04-distributed-and-parallel-computing/4.1-introduction.md 04-distributed-and-parallel-computing/4.2-distributed-computing.md 04-distributed-and-parallel-computing/4.3-parallel-computing.md 05-sequences-and-coroutines/5.1-introduction.md 05-sequences-and-coroutines/5.2-implicit-sequences.md 05-sequences-and-coroutines/5.3-coroutines.md -o ebook.epub --css style.css --epub-cover-image=cover.jpg 14 | ``` 15 | 16 | ## Bugs 17 | 18 | I know there are some formatting issues, I'm gonna quickly fix them, or you can open a PR. -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/cover.jpg -------------------------------------------------------------------------------- /ebook.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/ebook.epub -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/.DS_Store -------------------------------------------------------------------------------- /img/01/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/.DS_Store -------------------------------------------------------------------------------- /img/01/curves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/curves.png -------------------------------------------------------------------------------- /img/01/evaluate_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/evaluate_square.png -------------------------------------------------------------------------------- /img/01/evaluate_sum_squares_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/evaluate_sum_squares_0.png -------------------------------------------------------------------------------- /img/01/evaluate_sum_squares_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/evaluate_sum_squares_1.png -------------------------------------------------------------------------------- /img/01/evaluate_sum_squares_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/evaluate_sum_squares_2.png -------------------------------------------------------------------------------- /img/01/evaluate_sum_squares_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/evaluate_sum_squares_3.png -------------------------------------------------------------------------------- /img/01/expression_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/expression_tree.png -------------------------------------------------------------------------------- /img/01/function_abs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/function_abs.png -------------------------------------------------------------------------------- /img/01/function_print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/function_print.png -------------------------------------------------------------------------------- /img/01/global_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/global_frame.png -------------------------------------------------------------------------------- /img/01/global_frame_assignment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/global_frame_assignment.png -------------------------------------------------------------------------------- /img/01/global_frame_def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/global_frame_def.png -------------------------------------------------------------------------------- /img/01/iter_improve_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/iter_improve_apply.png -------------------------------------------------------------------------------- /img/01/iter_improve_global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/iter_improve_global.png -------------------------------------------------------------------------------- /img/01/newton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/newton.png -------------------------------------------------------------------------------- /img/01/pi_sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/pi_sum.png -------------------------------------------------------------------------------- /img/01/square_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/square_root.png -------------------------------------------------------------------------------- /img/01/square_root_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/01/square_root_update.png -------------------------------------------------------------------------------- /img/02/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/.DS_Store -------------------------------------------------------------------------------- /img/02/barriers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/barriers.png -------------------------------------------------------------------------------- /img/02/constraints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/constraints.png -------------------------------------------------------------------------------- /img/02/getitem_rlist_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/getitem_rlist_0.png -------------------------------------------------------------------------------- /img/02/getitem_rlist_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/getitem_rlist_1.png -------------------------------------------------------------------------------- /img/02/getitem_rlist_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/getitem_rlist_2.png -------------------------------------------------------------------------------- /img/02/interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/interface.png -------------------------------------------------------------------------------- /img/02/lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/lists.png -------------------------------------------------------------------------------- /img/02/multiple_inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/multiple_inheritance.png -------------------------------------------------------------------------------- /img/02/nested_pairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nested_pairs.png -------------------------------------------------------------------------------- /img/02/nonlocal_assign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_assign.png -------------------------------------------------------------------------------- /img/02/nonlocal_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_call.png -------------------------------------------------------------------------------- /img/02/nonlocal_call2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_call2.png -------------------------------------------------------------------------------- /img/02/nonlocal_corefer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_corefer.png -------------------------------------------------------------------------------- /img/02/nonlocal_def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_def.png -------------------------------------------------------------------------------- /img/02/nonlocal_def2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_def2.png -------------------------------------------------------------------------------- /img/02/nonlocal_recall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/nonlocal_recall.png -------------------------------------------------------------------------------- /img/02/pair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/pair.png -------------------------------------------------------------------------------- /img/02/sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/02/sequence.png -------------------------------------------------------------------------------- /img/03/eval_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/eval_apply.png -------------------------------------------------------------------------------- /img/03/fact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/fact.png -------------------------------------------------------------------------------- /img/03/factorial_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/factorial_machine.png -------------------------------------------------------------------------------- /img/03/fib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/fib.png -------------------------------------------------------------------------------- /img/03/fib_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/fib_env.png -------------------------------------------------------------------------------- /img/03/logo_apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/logo_apply.png -------------------------------------------------------------------------------- /img/03/logo_eval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/logo_eval.png -------------------------------------------------------------------------------- /img/03/pig_latin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/pig_latin.png -------------------------------------------------------------------------------- /img/03/scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/scope.png -------------------------------------------------------------------------------- /img/03/set_trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/set_trees.png -------------------------------------------------------------------------------- /img/03/sier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/sier.png -------------------------------------------------------------------------------- /img/03/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/star.png -------------------------------------------------------------------------------- /img/03/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/tree.png -------------------------------------------------------------------------------- /img/03/universal_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/03/universal_machine.png -------------------------------------------------------------------------------- /img/04/clientserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/04/clientserver.png -------------------------------------------------------------------------------- /img/04/deadlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/04/deadlock.png -------------------------------------------------------------------------------- /img/04/vector-math1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/04/vector-math1.png -------------------------------------------------------------------------------- /img/04/vector-math2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/04/vector-math2.png -------------------------------------------------------------------------------- /img/04/vector-math3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/04/vector-math3.png -------------------------------------------------------------------------------- /img/05/coroutine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/05/coroutine.png -------------------------------------------------------------------------------- /img/05/produce-filter-consume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/05/produce-filter-consume.png -------------------------------------------------------------------------------- /img/05/read-match-coroutine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/05/read-match-coroutine.png -------------------------------------------------------------------------------- /img/05/subroutine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florianmari/SICP-Python-ebook/ffd3b68f8a81325188ea7d676189cd4d38068ff7/img/05/subroutine.png -------------------------------------------------------------------------------- /introduction.md: -------------------------------------------------------------------------------- 1 | % Structure and Interpretation of Computer Programs (Python) 2 | % Abelson, Sussman, and Sussman. John Denero 3 | 4 | # Introduction 5 | 6 | ## SICP in Python 7 | 8 | [From: CS61A: Online Textbook](http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/) 9 | 10 | This book is derived from the classic textbook [Structure and Interpretation of Computer Programs](http://www-mitpress.mit.edu/sicp/full-text/book/book.html) by Abelson, Sussman, and Sussman. John Denero originally modified if for Python for the Fall 2011 semester. It is licensed under the [Creative Commons Attribution-ShareAlike 3.0 license](http://creativecommons.org/licenses/by-sa/3.0/). -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* This defines styles and classes used in the book */ 2 | body { margin: 5%; text-align: justify; font-size: medium; } 3 | code { font-family: monospace; } 4 | h1 { text-align: left; } 5 | h2 { text-align: left; } 6 | h3 { text-align: left; } 7 | h4 { text-align: left; } 8 | h5 { text-align: left; } 9 | h6 { text-align: left; } 10 | /* For title, author, and date on the cover page */ 11 | h1.title { } 12 | p.author { } 13 | p.date { } 14 | nav#toc ol, 15 | nav#landmarks ol { padding: 0; margin-left: 1em; } 16 | nav#toc ol li, 17 | nav#landmarks ol li { list-style-type: none; margin: 0; padding: 0; } 18 | a.footnote-ref { vertical-align: super; } 19 | em, em em em, em em em em em { font-style: italic;} 20 | em em, em em em em { font-style: normal; } 21 | pre, 22 | code { 23 | font-size: 90%; 24 | line-height: 100%; 25 | } 26 | 27 | pre { 28 | background-color: #f7f7f7; 29 | overflow: auto; 30 | word-wrap: normal; 31 | padding: .85em 1em; 32 | margin: 0 0 1.275em; 33 | font-family: monospace; 34 | } 35 | 36 | blockquote { 37 | color: #666666; 38 | margin: 1em 0; 39 | padding-left: 1.5em; 40 | border-left: 0.5em #eee solid; 41 | } 42 | span.smallcaps{ font-variant: small-caps; } 43 | span.underline{ text-decoration: underline; } 44 | q { quotes: "“" "”" "‘" "’"; } 45 | div.column{ display: inline-block; vertical-align: top; width: 50%; } 46 | div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 47 | @media screen { /* Workaround for iBooks issue; see #6242 */ 48 | .sourceCode { 49 | overflow: visible !important; 50 | white-space: pre-wrap !important; 51 | } 52 | } --------------------------------------------------------------------------------